Production-grade email delivery using AWS Simple Email Service (SES) with bounce handling, suppression management, and observability.
Architecture
Truthlocks uses AWS SES for all transactional emails. The integration includes:
- SES Provider: Direct integration with SES v2 API for sending emails
- SNS Webhooks: Real-time bounce/complaint notifications via SNS topics
- Suppression List: Automatic suppression of hard bounces and complaints
- Environment Gating: Log-only mode for development, SES for production
AWS SES Setup
1. Verify Domain
Verify your sending domain in SES console:
# Using AWS CLI
aws ses verify-domain-identity --domain truthlocks.com --region us-east-1
# Get DKIM tokens (add to DNS)
aws ses verify-domain-dkim --domain truthlocks.com --region us-east-1
Add the following records to your domain DNS:
| Type | Name | Value |
|---|
| TXT | _amazonses.truthlocks.com | (verification token from SES) |
| CNAME | (DKIM selector 1) | (DKIM value from SES) |
| MX | mail.truthlocks.com | feedback-smtp.us-east-1.amazonses.com |
| TXT | mail.truthlocks.com | v=spf1 include:amazonses.com ~all |
3. Request Production Access
New SES accounts are in sandbox mode. Request production access in the SES console to send emails to unverified addresses.
Environment Variables
| Variable | Required | Description |
|---|
EMAIL_MODE | Yes | log (dev) or ses (prod) |
SES_REGION | If ses | AWS region (e.g., us-east-1) |
SES_FROM | If ses | Sender address (e.g., no-reply@truthlocks.com) |
In ENV=production, the service will fail to start if SES_REGION and
SES_FROM are not configured.
Bounce & Complaint Handling
SES notifications are received via an SNS webhook at /v1/notifications/ses-events.
Event Types
- Bounce (Permanent): Hard bounce → Email suppressed automatically
- Bounce (Transient): Soft bounce → Logged but not suppressed
- Complaint: User marked as spam → Email suppressed automatically
- Delivery: Successful delivery → Logged for observability
Suppression List Management
Suppressed emails are stored in the email_suppressions table and will not receive future emails. The API returns EMAIL_SUPPRESSED error code.
-- Check if email is suppressed
SELECT * FROM email_suppressions WHERE email = 'user@example.com';
-- View recent suppressions
SELECT email, reason, suppressed_at
FROM email_suppressions
ORDER BY suppressed_at DESC
LIMIT 20;
Troubleshooting
Email not received
- Check logs for
email_sent or email_send_failed events
- Verify the recipient email is not in
email_suppressions
- Check SES console for sending quota and reputation
- Verify DKIM/SPF records are correctly configured
Observability
All email operations are logged with structured fields:
{
"level": "info",
"msg": "email_sent",
"message_id": "0102...",
"to": "user@example.com",
"subject": "Welcome to Truthlocks",
"template_name": "issuer-onboarding-invite",
"duration_ms": 245
}