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¶
- Add method to
EmailTemplatesclass intemplates.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..."
)
- Add send method to
EmailServiceclass inservice.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¶
-
Set environment variable:
-
Test via Python:
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
Related Documentation¶
- Environment Variables Guide
- PRD: Notifications & Engagement (see
docs/archive/historical/production-launch-prd-v1.md) - Background Service