Email System Documentation

Email Templates & Purposes

1. Welcome Email

2. Badge Email

3. Check-in Reminder Email

4. First Check-in Email

5. Level Completed Email

6. Goal Reached Email

7. Contact Form Email

Email Evaluation & Sending Details

This section provides comprehensive information about when each email is evaluated and sent, including code locations, conditions, and timing.

1. Welcome Email

When Evaluated: Immediately when Clerk user.created webhook is received

Code Location: - Handler: internal/handlers/clerk_webhooks.gohandleUserCreated() (line 181) - Send Method: sendWelcomeEmailAsync() (line 237) → EmailService.SendWelcomeEmail()

Evaluation Flow:

Clerk Webhook → handleUserCreated() → sendWelcomeEmailAsync() → 
  Check rate limit (24h) → SendWelcomeEmail() → Record notification

Conditions: 1. User must have an email address in Clerk data 2. SMTP must be configured (IsEmailConfigured()) 3. No welcome email sent in last 24 hours (HasRecentEmailNotification(userID, EmailTypeWelcome, 24))

Timing: Sent immediately after user creation (async goroutine, non-blocking)

Rate Limiting: 24 hours


2. Badge Email

When Evaluated: Immediately after journey creation

Code Location: - Handler: internal/api/journeys.goHandleJourneys() POST (line 82) - Send Method: sendFirstJourneyBadgeIfNeeded() (line 579) → EmailService.SendBadgeEmail()

Evaluation Flow:

POST /api/journeys → HandleJourneys() → sendFirstJourneyBadgeIfNeeded() → 
  Check journey count (must be 1) → Check rate limit (24h) → 
  SendBadgeEmail() → Record notification

Conditions: 1. User must have exactly 1 journey (counted via CountUserJourneys()) 2. User must have valid email in settings 3. SMTP must be configured 4. No badge email sent in last 24 hours

Timing: Sent immediately after first journey creation (async goroutine, non-blocking)

Rate Limiting: 24 hours


3. Check-in Reminder Email

When Evaluated: Continuously by background service

Code Location: - Service: internal/service/notifications.goNotificationService.checkForMissingCheckins() (see function checkForMissingCheckins) - Service Start: NotificationService.Start() called in main.go - Send Method: sendCheckinReminder()EmailService.SendCheckinReminder() (see notifications.go)

Service Configuration: - Check Interval: Configurable via DEFAULT_NOTIFICATION_CHECKPOINT_RATE (default: 1 minute) - Email Time: Configurable via DEFAULT_NOTIFICATION_CHECKPOINT_TIME (default: “08:08”) - Rate Limiting: Configurable via DEFAULT_NOTIFICATION_SPAM_CONTROL (default: 24 hours)

Evaluation Flow:

Background Service (every 1 min) → checkForMissingCheckins() → 
  Get all active journeys → Filter by user settings → 
  Check if check-in day → Check if email time → 
  Find missing check-ins → Check rate limit → 
  sendCheckinReminder() → SendCheckinReminder() → Record notification

Evaluation Conditions (ALL must be met): 1. ✅ User has notifications enabled (settings.NotificationsEnabled == true) 2. ✅ It’s the user’s check-in day (userDayOfWeek == settings.DefaultCheckinDay) 3. ✅ Current time matches email time in user’s timezone (isEmailTimeForUser()) 4. ✅ User has active journey with missing check-ins: - Level has EndWeight == 0 (no check-in recorded) - Level StartDate is in the past or today - Level status is not LevelCompleted 5. ✅ No check-in reminder sent within spam control interval (default: 24 hours)

Timing: - Service runs: Continuously in background - Checks performed: Every DEFAULT_NOTIFICATION_CHECKPOINT_RATE (default: 1 minute) - Email sent: When current time in user’s timezone matches DEFAULT_NOTIFICATION_CHECKPOINT_TIME (default: 08:08) - User timezone: Respects settings.TimeZone (default: America/New_York per model.DefaultTimeZone)

Rate Limiting: Configurable (default: 24 hours)


4. First Check-in Email

When Evaluated: When a journey is updated and a level is completed

Code Location: - Handler: internal/api/journeys.gocheckAndSendLevelEmails() (line 655) - Called From: - HandleUpdateJourney() PUT handler (line 243) - HandleUpdateJourneyLevel() PATCH handler (line 351) - Send Method: sendFirstCheckinEmail() (line 725) → EmailService.SendFirstCheckinEmail()

Evaluation Flow:

PUT/PATCH /api/journeys → Update journey → checkAndSendLevelEmails() → 
  Detect newly completed level → Check if first completed → 
  sendFirstCheckinEmail() → Check rate limit (24h) → 
  SendFirstCheckinEmail() → Record notification

Conditions: 1. Level status changed to LevelCompleted 2. Level has EndWeight > 0 3. This is the first completed level for the user 4. No first check-in email sent in last 24 hours

Timing: Sent immediately when first level is completed (async goroutine, non-blocking)

Rate Limiting: 24 hours


5. Level Completed Email

When Evaluated: When a journey is updated and any level is completed

Code Location: - Handler: internal/api/journeys.gocheckAndSendLevelEmails() (line 655) - Called From: - HandleUpdateJourney() PUT handler (line 243) - HandleUpdateJourneyLevel() PATCH handler (line 351) - Send Method: sendLevelCompletedEmail() (line 761) → EmailService.SendLevelCompletedEmail()

Evaluation Flow:

PUT/PATCH /api/journeys → Update journey → checkAndSendLevelEmails() → 
  Detect newly completed level → sendLevelCompletedEmail() → 
  SendLevelCompletedEmail() → Record notification

Conditions: 1. Level status changed to LevelCompleted 2. Level has EndWeight > 0 3. Level was not previously completed (comparison with old journey state)

Timing: Sent immediately when any level is completed (async goroutine, non-blocking)

Rate Limiting: None (sent for each completion)


6. Goal Reached Email

When Evaluated: When a journey is updated and goal weight is reached

Code Location: - Handler: internal/api/journeys.gocheckAndSendLevelEmails() (line 655, lines 709-721) - Called From: - HandleUpdateJourney() PUT handler (line 243) - HandleUpdateJourneyLevel() PATCH handler (line 351) - Send Method: sendGoalReachedEmail() (line 792) → EmailService.SendGoalReachedEmail()

Evaluation Flow:

PUT/PATCH /api/journeys → Update journey → checkAndSendLevelEmails() → 
  Check if goal reached → Check if just reached → 
  sendGoalReachedEmail() → Check rate limit (1 week) → 
  SendGoalReachedEmail() → Record notification

Conditions: 1. Journey has TargetWeight > 0 and CurrentWeight > 0 2. Goal weight threshold crossed: - If StartWeight > TargetWeight: CurrentWeight <= TargetWeight (weight loss) - If StartWeight < TargetWeight: CurrentWeight >= TargetWeight (weight gain) 3. Journey status changed to StatusCompleted OR goal was just reached 4. No goal reached email sent in last 1 week

Timing: Sent immediately when goal is reached (async goroutine, non-blocking)

Rate Limiting: 1 week (168 hours)


7. Contact Form Email

When Evaluated: Immediately when contact form is submitted

Code Location: - Handler: internal/api/contact.goHandleContact() POST - Send Method: sendContactEmailAsync() (goroutine) → sendContactEmail()email.GenerateContactEmailHTML()

Evaluation Flow:

POST /api/contact → HandleContact() → Validate request →
  go sendContactEmailAsync(req) → Return 200 immediately →
  (async) sendContactEmail() → GenerateContactEmailHTML() → Send via gomail

Conditions: 1. Valid contact form submission 2. SMTP configured (checked inside async goroutine)

Timing: Queued immediately upon form submission (asynchronous, does not block request)

Rate Limiting: None


Email Service Architecture

Service Structure

Synchronous vs Asynchronous

Evaluation Timing Summary

  1. Immediate: Welcome, Badge, First Check-in, Level Completed, Goal Reached, Contact Form
  2. Scheduled: Check-in Reminder (background service with time-based triggers)

Rate Limiting Summary

Email Type Rate Limit Check Method
Welcome 24 hours HasRecentEmailNotification(userID, EmailTypeWelcome, 24)
Badge 24 hours HasRecentEmailNotification(userID, EmailTypeBadgeAward, 24)
Check-in Reminder Configurable (default: 24h) HasRecentEmailNotification(userID, EmailTypeCheckinReminder, spamControl)
First Check-in 24 hours HasRecentEmailNotification(userID, EmailTypeFirstCheckin, 24)
Level Completed None N/A (sent for each completion)
Goal Reached 1 week (168h) HasRecentEmailNotification(userID, EmailTypeGoalReached, 168)
Contact Form None N/A

Database Tracking

Error Handling

Email Configuration

Environment Variables Required:

Notification Service Environment Variables:

Email Service Location:

Test Endpoint

Test Email Endpoint:

Simple Email Test

Send a POST request to /api/test-emails with JSON body:

{
  "email": "your-email@example.com"
}

Example using curl:

curl -X POST http://localhost:8080/api/test-emails \
  -H "Content-Type: application/json" \
  -d '{"email": "your-email@example.com"}'

Response on success (all emails sent):

{
  "success": true,
  "message": "Test emails sent to your-email@example.com",
  "email": "your-email@example.com",
  "total_sent": 7,
  "successful": 7,
  "failed": 0,
  "results": {
    "welcome": "Sent successfully",
    "badge": "Sent successfully",
    "checkin_reminder": "Sent successfully",
    "first_checkin": "Sent successfully",
    "level_completed": "Sent successfully",
    "goal_reached": "Sent successfully",
    "contact_form": "Sent successfully"
  },
  "email_types": {
    "welcome": "Welcome Email - Sent to new users when they sign up",
    "badge": "Badge Email - Sent when a user earns an achievement badge",
    "checkin_reminder": "Check-in Reminder Email - Reminds users to complete their weekly check-ins",
    "first_checkin": "First Check-in Email - Celebrates the user's first completed check-in",
    "level_completed": "Level Completed Email - Congratulates users on completing a level",
    "goal_reached": "Goal Reached Email - Celebrates reaching the target weight goal",
    "contact_form": "Contact Form Email - Sends contact form submissions to support team"
  }
}

Response on partial failure:

{
  "success": false,
  "message": "Test emails sent to your-email@example.com",
  "email": "your-email@example.com",
  "total_sent": 7,
  "successful": 5,
  "failed": 2,
  "results": {
    "welcome": "Sent successfully",
    "badge": "Sent successfully",
    "checkin_reminder": "Failed: SMTP connection timeout",
    "first_checkin": "Sent successfully",
    "level_completed": "Sent successfully",
    "goal_reached": "Sent successfully",
    "contact_form": "Failed: invalid email address"
  },
  "email_types": { ... }
}

Note: The endpoint sends all 7 email types sequentially with a 1-second delay between each email to avoid overwhelming the SMTP server. Check your inbox (and spam folder) for all email samples.

Potential Issues

Common Email Delivery Problems:

  1. SMTP configuration missing or incorrect
  2. SMTP authentication failures
  3. Email service not initialized properly
  4. Network/firewall blocking SMTP connections
  5. Email provider rate limiting
  6. Invalid email addresses
  7. Email marked as spam
  8. Template generation errors

Debug Hypotheses

Hypothesis A: SMTP Configuration Missing or Incorrect

Hypothesis B: SMTP Configuration Check Failing

Hypothesis C: SMTP Port Parsing Issue

Hypothesis D: Email Message Creation Failure

Hypothesis E: SMTP Connection Failure

Hypothesis F: SMTP Authentication Failure

Hypothesis G: Email Sent but Not Received