Skip to content

Email Notification System

Status: Production Ready Last Updated: January 10, 2026 Provider: Resend (resend.com)


Overview

Archety uses transactional emails for key user lifecycle events including onboarding, billing, and account management. All emails are sent via Resend API with responsive HTML templates matching Archety's brand identity.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Email System                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────────────┐    ┌──────────────────┐                   │
│  │  Email Service   │───▶│  Resend API      │                   │
│  │  (service.py)    │    │  (External)      │                   │
│  └────────┬─────────┘    └──────────────────┘                   │
│           │                                                      │
│           │ uses                                                 │
│           ▼                                                      │
│  ┌──────────────────┐                                           │
│  │  Email Templates │                                           │
│  │  (templates.py)  │                                           │
│  └──────────────────┘                                           │
│                                                                  │
├─────────────────────────────────────────────────────────────────┤
│                       Trigger Points                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────────────┐    ┌──────────────────┐                   │
│  │ Onboarding       │    │ Payment Service  │                   │
│  │ Routes           │    │ (Webhooks)       │                   │
│  └────────┬─────────┘    └────────┬─────────┘                   │
│           │                       │                              │
│           │ Welcome               │ Receipt, Failed,             │
│           │ Email                 │ Cancelled                    │
│           ▼                       ▼                              │
│  ┌──────────────────────────────────────────┐                   │
│  │              Email Service               │                   │
│  └──────────────────────────────────────────┘                   │
│           ▲                       ▲                              │
│           │                       │                              │
│           │ Deletion              │ Trial                        │
│           │ Scheduled             │ Reminders                    │
│  ┌────────┴─────────┐    ┌───────┴──────────┐                   │
│  │ Account Routes   │    │ Scheduler        │                   │
│  │                  │    │ (Daily Job)      │                   │
│  └──────────────────┘    └──────────────────┘                   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

File Structure

app/email/
├── __init__.py           # Module exports
├── service.py            # EmailService class with Resend integration
└── templates.py          # EmailTemplates class with HTML templates

app/scheduler/
└── email_jobs.py         # EmailJobRunner for scheduled email tasks

Configuration

Environment Variables

Variable Description Example
EMAIL_API_KEY Resend API key re_XXXXX...
EMAIL_PROVIDER Email provider (currently only "resend") resend
EMAIL_FROM_ADDRESS From address with display name Sage <sage@archety.ai>
EMAIL_FROM_NAME Display name (deprecated, use FROM_ADDRESS) Sage
EMAIL_REPLY_TO Reply-to address support@archety.ai
EMAIL_ENABLED Master toggle for email sending true

Config File Location

app/config.py (Settings class):

# Email Configuration (Resend)
email_provider: str = "resend"
email_api_key: Optional[str] = None
email_from_address: str = "Sage <sage@archety.ai>"
email_from_name: str = "Sage"
email_reply_to: str = "support@archety.ai"
email_enabled: bool = True

Email Types

1. Welcome Email

Trigger: Onboarding completion (POST /onboarding/complete) File: app/api/onboarding_routes.py:493-506

from app.email.service import get_email_service

email_service = get_email_service()
email_service.send_welcome_email(
    user_email=user.email,
    user_name=user.name
)

Template Features: - Personalized greeting - References photo personality analysis - Quick start guide with capabilities - CTA to start chatting


2. Trial Ending Reminder (3-day)

Trigger: Daily scheduled job at 9am File: app/scheduler/email_jobs.py

email_service.send_trial_ending_email(
    user_email=user.email,
    user_name=user.name,
    days_remaining=3
)

Template Features: - Urgency messaging - List of what user keeps with subscription - Subscribe CTA button - 30-day data retention notice


3. Trial Ending Reminder (1-day)

Trigger: Daily scheduled job at 9am File: app/scheduler/email_jobs.py

email_service.send_trial_ending_email(
    user_email=user.email,
    user_name=user.name,
    days_remaining=1
)

4. Payment Receipt

Trigger: Stripe webhook invoice.payment_succeeded File: app/payment/service.py:664-691

email_service.send_payment_receipt(
    user_email=user.email,
    user_name=user.name,
    amount=amount_dollars,
    description="Archety Subscription",
    invoice_url=invoice_url
)

Template Features: - Payment details table (date, description, amount) - Invoice link button (if available) - Green success styling


5. Payment Failed

Trigger: Stripe webhook invoice.payment_failed File: app/payment/service.py:743-766

email_service.send_payment_failed(
    user_email=user.email,
    user_name=user.name,
    update_payment_url=billing_portal_url
)

Template Features: - Action required messaging - Update payment method CTA - Amber/warning styling - Retry information


6. Subscription Cancelled

Trigger: Stripe webhook customer.subscription.deleted File: app/payment/service.py:612-625

email_service.send_subscription_cancelled(
    user_email=user.email,
    user_name=user.name,
    end_date=user.subscription_current_period_end
)

Template Features: - Access end date information - Reactivate CTA button - 30-day data retention notice - Feedback request


7. Account Deletion Scheduled

Trigger: Account deletion request (DELETE /account) File: app/api/account_routes.py:221-238

email_service.send_account_deletion_scheduled(
    user_email=user.email,
    user_name=user.name,
    deletion_date=deletion_date,
    cancel_url=f"{settings.frontend_base_url}/dashboard/settings?action=cancel-deletion"
)

Template Features: - Deletion date prominently displayed - List of what will be deleted - Cancel deletion CTA button - Red/warning styling - Security notice


Scheduled Jobs

Trial Expiration Check

Schedule: Daily at 9:00 AM Job ID: trial_expiration_check File: app/scheduler/background_service.py:138-144

self.scheduler.add_job(
    self.run_trial_expiration_check,
    CronTrigger(hour=9, minute=0),
    id='trial_expiration_check',
    name='Trial Expiration Email Check'
)

Logic: 1. Acquires distributed lock (23-hour TTL) 2. Queries users with subscription_status = 'trial' 3. Sends 3-day reminder to users expiring in 3 days 4. Sends 1-day reminder to users expiring tomorrow 5. Uses Redis to prevent duplicate emails (7-day dedup key)

Deduplication: - Redis key: trial_reminder:{3_day|1_day}:{user_id} - TTL: 7 days - Prevents re-sending same reminder type to same user


Email Service API

Initialization

from app.email.service import get_email_service

email_service = get_email_service()  # Singleton instance

Available Methods

Method Parameters Returns
send_welcome_email() user_email, user_name? (success, email_id, error)
send_trial_ending_email() user_email, user_name?, days_remaining (success, email_id, error)
send_payment_receipt() user_email, user_name?, amount, description, invoice_url?, receipt_date? (success, email_id, error)
send_subscription_cancelled() user_email, user_name?, end_date? (success, email_id, error)
send_account_deletion_scheduled() user_email, user_name?, deletion_date?, cancel_url? (success, email_id, error)
send_payment_failed() user_email, user_name?, update_payment_url? (success, email_id, error)
is_available() - bool

Return Values

All send methods return a tuple: (success: bool, email_id: Optional[str], error: Optional[str])

success, email_id, error = email_service.send_welcome_email(
    user_email="user@example.com",
    user_name="John"
)

if success:
    logger.info(f"Email sent: {email_id}")
else:
    logger.warning(f"Email failed: {error}")

Template System

Brand Colors

BRAND_PRIMARY = "#6366f1"    # Indigo-500
BRAND_SECONDARY = "#8b5cf6"  # Violet-500
BRAND_DARK = "#1e1b4b"       # Indigo-950
BRAND_LIGHT = "#f5f3ff"      # Violet-50
TEXT_PRIMARY = "#1f2937"     # Gray-800
TEXT_SECONDARY = "#6b7280"   # Gray-500

Template Structure

All templates use EmailTemplates._base_template() which provides: - Responsive HTML structure - Mobile-friendly CSS (max-width: 600px) - Archety header with logo - Footer with support info and legal links - Preview text support for email clients

Adding New Templates

  1. Add method to EmailTemplates class in templates.py:
@classmethod
def new_email_template(cls, user_name: Optional[str] = None, ...) -> str:
    greeting = f"Hey {user_name}," if user_name else "Hey there,"

    content = f"""
    <h1>Email Title</h1>
    <p>{greeting}</p>
    <p>Email content here...</p>
    """

    return cls._base_template(
        content,
        preview_text="Preview text for email clients..."
    )
  1. Add send method to EmailService class in service.py:
def send_new_email(
    self,
    user_email: str,
    user_name: Optional[str] = None,
    ...
) -> Tuple[bool, Optional[str], Optional[str]]:
    subject = "Email Subject"
    html_content = EmailTemplates.new_email_template(user_name, ...)

    return self._send_email(
        to_email=user_email,
        subject=subject,
        html_content=html_content,
        tags=[{"name": "type", "value": "new_email"}]
    )

Error Handling

Graceful Degradation

Email failures never block the main operation:

try:
    email_service.send_welcome_email(...)
except Exception as email_error:
    # Don't fail the request if email fails
    logger.warning(f"Failed to send email: {email_error}")

Service Unavailability

When Resend is unavailable: - is_available() returns False - Send methods log the email details but return (False, None, "Email service not available") - No exceptions thrown

Resend Error Types

import resend.exceptions

try:
    email = resend.Emails.send(params)
except resend.exceptions.ValidationError as e:
    # Invalid email format, missing fields
except resend.exceptions.RateLimitError as e:
    # Too many requests
except resend.exceptions.ResendError as e:
    # General API error

Testing

Local Testing

  1. Set environment variable:

    export EMAIL_API_KEY=re_test_...
    export EMAIL_ENABLED=true
    

  2. Test via Python:

    from app.email.service import get_email_service
    
    email_service = get_email_service()
    success, email_id, error = email_service.send_welcome_email(
        user_email="your-email@example.com",
        user_name="Test User"
    )
    print(f"Success: {success}, ID: {email_id}, Error: {error}")
    

Disable Emails

Set EMAIL_ENABLED=false or remove EMAIL_API_KEY to disable email sending. Emails will be logged but not sent.


Monitoring

Logs

Email operations are logged at INFO level:

INFO - Email sent successfully: re_abc123 to user@example.com
WARNING - Failed to send welcome email: Rate limit exceeded

Resend Dashboard

Monitor delivery at: https://resend.com/emails

  • View sent emails
  • Check delivery status
  • Review bounces and complaints

Tags

All emails include tags for filtering in Resend: - type: Email type (welcome, payment_receipt, etc.) - category: High-level category (onboarding, billing, account)


Future Enhancements

  • Feature highlight email (Day 2 of onboarding)
  • Calendar connect prompt email (Day 5)
  • Re-engagement emails (3-day, 7-day, 14-day nudges)
  • Upcoming renewal reminder (3 days before)
  • Export completion notification
  • Unsubscribe handling for marketing emails