Add onboarding email campaign system with post-verification welcome email
Implements automated onboarding emails to encourage user engagement: - Post-verification welcome email with 5 tips (sent after email verification) - "No Residence" email (2+ days after registration with no property) - "No Tasks" email (5+ days after first residence with no tasks) Key features: - Each onboarding email type sent only once per user (enforced by unique constraint) - Email open tracking via tracking pixel endpoint - Daily scheduled job at 10:00 AM UTC to process eligible users - Admin panel UI for viewing sent emails, stats, and manual sending - Admin can send any email type to users from the user detail Testing section New files: - internal/models/onboarding_email.go - Database model with tracking - internal/services/onboarding_email_service.go - Business logic and eligibility queries - internal/handlers/tracking_handler.go - Email open tracking endpoint - internal/admin/handlers/onboarding_handler.go - Admin API endpoints - admin/src/app/(dashboard)/onboarding-emails/ - Admin UI pages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
34
internal/models/onboarding_email.go
Normal file
34
internal/models/onboarding_email.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// OnboardingEmailType represents the type of onboarding email
|
||||
type OnboardingEmailType string
|
||||
|
||||
const (
|
||||
// OnboardingEmailNoResidence is sent when a user has not created a residence after 2 days
|
||||
OnboardingEmailNoResidence OnboardingEmailType = "no_residence"
|
||||
// OnboardingEmailNoTasks is sent when a user has created a residence but no tasks after 5 days
|
||||
OnboardingEmailNoTasks OnboardingEmailType = "no_tasks"
|
||||
)
|
||||
|
||||
// OnboardingEmail tracks sent onboarding emails per user
|
||||
// Each email type can only be sent once per user (enforced by unique constraint on user_id + email_type)
|
||||
type OnboardingEmail struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||
EmailType OnboardingEmailType `gorm:"type:varchar(50);not null;index" json:"email_type"`
|
||||
SentAt time.Time `gorm:"not null" json:"sent_at"`
|
||||
OpenedAt *time.Time `json:"opened_at"`
|
||||
TrackingID string `gorm:"type:varchar(64);uniqueIndex" json:"tracking_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for OnboardingEmail
|
||||
func (OnboardingEmail) TableName() string {
|
||||
return "onboarding_emails"
|
||||
}
|
||||
Reference in New Issue
Block a user