Email Provider Migration: Resend → SES → Postmark → Mailgun
Status: COMPLETE for app sends. Mailgun delivers everything the app sends (KineticAI, Sandbox, workspace, broadcasts, automations). No app code calls Resend any more, but Resend is still the SMTP provider Supabase Auth uses for sign-in, signup-confirmation and password-reset email (sender auth@e.kinetic.email), so its DNS records on kinetic.email and e.kinetic.email must stay while that is true. Amazon SES is not used at all — the amazonses.com records under send.* belong to Resend, which runs on SES.
Timeline
- Pre-April 2026: Resend as primary email provider (no raw MIME / AMP support)
- April 2-8: Attempted AWS SES migration for AMP4Email support via raw MIME
- April 8: SES production access denied twice despite detailed application
- April 9: Attempted Postmark migration — domain verified, but REST API does not support raw MIME or AMP4Email
- April 9: Migrated to Mailgun — raw MIME via REST API confirmed working, AMP4Email delivered successfully
- Post-April 2026: Resend retired from app sending — all sends (AMP and non-AMP) go through Mailgun;
api/send-email.tsandRESEND_API_KEYremoved. Resend remains only as Supabase Auth's SMTP provider, configured in the Supabase dashboard rather than in this repo
Why SES Failed
AWS SES requires manual approval to exit sandbox mode. We submitted two detailed applications including full bounce/complaint handling, unsubscribe infrastructure, and CAN-SPAM compliance. Both were rejected with generic responses and no actionable feedback.
Why Postmark Failed
Postmark's REST API does not support raw MIME (RawSource is undocumented and rejected From headers). Their documentation has no mention of AMP4Email support. The Invalid 'From' value error persisted despite correct Sender Signature configuration. Postmark is not suitable for AMP4Email.
Why Mailgun
- Raw MIME via REST API —
POST /v3/{domain}/messages.mimeaccepts full MIME messages asmultipart/form-data - AMP4Email support — Explicitly documented.
text/x-amp-htmlMIME part works out of the box - No sandbox gatekeeping — Domain verification grants immediate sending to any address
- Free tier — 100 emails/day (~3,000/month), no credit card, no time limit
- Webhook support — Delivered, failed, complained, opened, clicked events
- HMAC-SHA256 webhook verification — Signing key for secure webhook authentication
Current Architecture
| Provider | Purpose | Endpoints |
|---|---|---|
| Mailgun | All app sends — AMP4Email (raw MIME) and standard HTML | api/send-email-mailgun.ts |
| Resend | Supabase Auth email only — magic link, signup confirmation, password reset | None — Supabase → Authentication → SMTP Settings |
How It Works
- Every send goes through
/api/send-email-mailgun(KineticAI, Sandbox, workspace, etc.) - Sends require a signed-in user and are attributed to the verified account email
- The sending domain and from address default to
kinetic.email/Kinetic Email <ai@kinetic.email>and can be overridden viaMAILGUN_DOMAIN/MAILGUN_FROMwithout a deploy
Webhook Flow
Mailgun webhooks → api/mailgun-webhook.ts → logs to email_delivery_events table + feeds bounces/complaints into email_suppressions table → viewable at /station/delivery
Environment Variables
| Variable | Where | Purpose |
|---|---|---|
MAILGUN_API | Vercel + .env.local | Mailgun API key |
MAILGUN_WEBHOOK_SIGNING_KEY | Vercel | Webhook signature verification |
MAILGUN_DOMAIN | Vercel (optional) | Sending domain override (defaults to kinetic.email) |
MAILGUN_FROM | Vercel (optional) | From address override (defaults to Kinetic Email <ai@kinetic.email>) |
Removed
| Variable | Reason |
|---|---|
RESEND_API_KEY | No app code sends through Resend — all app sends via Mailgun. Auth email still uses a Resend key, but it lives in Supabase's SMTP settings, not in any app environment |
EMAIL_FROM | Was Resend's sender address; Mailgun uses MAILGUN_FROM |
POSTMARK_SERVER_TOKEN | Postmark does not support AMP4Email |
AWS_ACCESS_KEY_ID | SES sandbox access denied |
AWS_SECRET_ACCESS_KEY | SES sandbox access denied |
AWS_REGION | SES sandbox access denied |
AWS_SNS_TOPIC_ARN | SES sandbox access denied |
File Inventory
| File | Status | Purpose |
|---|---|---|
api/send-email-mailgun.ts | Active | Mailgun send endpoint — all sends, with AMP raw MIME support |
api/mailgun-webhook.ts | Active | Mailgun delivery/bounce/complaint webhook (HMAC-verified) |
api/unsubscribe.ts | Active | Global unsubscribe endpoint (verified session or signed HMAC token) |
database/68-email-suppressions.sql | Active | Suppression tables |
database/69-email-delivery-events.sql | Active | Delivery event log for Station dashboard |
Removed Files
| File | Reason |
|---|---|
api/send-email.ts | Resend retired — Mailgun handles non-AMP sends too |
api/send-email-postmark.ts | Postmark does not support AMP |
api/postmark-webhook.ts | Postmark removed |
api/send-email-ses.ts | SES access denied |
api/ses-webhook.ts | SES removed |
AMP4Email Setup
Sender Requirements
- Domain
kinetic.emailverified in Mailgun with DKIM + SPF - DMARC set to
p=rejectwithrua=mailto:dmarc@kinetic.email
Recipient Requirements
None. kinetic.email is an approved AMP sender for Gmail and Yahoo Mail, and dynamic email is on by default in Gmail. A recipient who has turned dynamic email off (or whose Google Workspace admin has) gets the HTML version.
MIME Structure
AMP emails are sent as multipart/alternative with three parts:
text/plain— plain text fallbacktext/x-amp-html— AMP version (rendered in Gmail, Yahoo)text/html— HTML fallback (rendered in all other clients)