How AI Sends Email (And Why Every Pattern Is Wrong)
AI generates email code with four consistent mistakes: sending synchronously in the request handler (blocks the user for 2-5 seconds while SMTP connects), hardcoding SMTP credentials in the code (not environment variables), building HTML emails with string concatenation (renders inconsistently across email clients), and no error handling (if sending fails, the user action fails too — signup blocked because email failed).
Email sending is slow (SMTP handshake + message transfer = 1-5 seconds), unreliable (SMTP servers reject, rate limit, and timeout), and complex (HTML that works in Gmail breaks in Outlook, which breaks in Apple Mail). AI treats it like a simple function call — send and forget. Production email requires: async processing, error handling with retry, HTML templates that render across clients, and deliverability configuration.
These rules cover: transactional email services (Resend, SendGrid, Postmark), template systems (React Email, MJML), async sending via queues, and deliverability basics (SPF, DKIM, DMARC).
Rule 1: Send Email Asynchronously — Never in the Request Handler
The rule: 'Never send email in the HTTP request handler. Queue the email for background processing: await emailQueue.add({ to: user.email, template: "welcome", data: { name: user.name } }). Return the response immediately — the user does not wait for email delivery. Process the queue with a background worker: send, retry on failure, log results. The user action (signup, purchase, password reset) succeeds regardless of email delivery status.'
For the queue: 'Use BullMQ (Node.js + Redis), Celery (Python + Redis), or your cloud platform queue (SQS, Cloud Tasks, Vercel Cron). Configure retry: 3 attempts with exponential backoff. Configure dead letter queue for permanently failed sends. Monitor: emails sent, failed, retried, in queue depth.'
AI generates: await sendEmail(to, subject, html); return res.json({ success: true }). If SMTP takes 3 seconds, the response is delayed 3 seconds. If SMTP fails, the request fails. Queueing decouples the email from the request — the user action succeeds instantly, the email arrives in the background.
- Queue email for background sending — never await in request handler
- User action succeeds regardless of email status — decouple
- BullMQ/Celery/SQS for the queue — retry 3x with exponential backoff
- Dead letter queue for permanent failures — monitor queue depth
- Response in <100ms — email arrives in 1-30 seconds in background
await sendEmail() in the request handler blocks the response 1-5 seconds. If SMTP fails, the user action fails. Queue the email for background processing — the response returns in <100ms, the email arrives in seconds.
Rule 2: Transactional Email Services, Not Raw SMTP
The rule: 'Use a transactional email service: Resend (modern, developer-friendly), SendGrid (enterprise, high volume), Postmark (deliverability-focused), or AWS SES (cost-effective, high volume). Never configure raw SMTP — it requires: server maintenance, IP reputation management, bounce handling, and compliance management. Email services handle all of this.'
For the API: 'Send via HTTP API, not SMTP: await resend.emails.send({ from: "noreply@yourapp.com", to: user.email, subject: "Welcome", react: WelcomeEmail({ name: user.name }) }). HTTP API is faster than SMTP (no handshake), more reliable (retry built in), and simpler (one function call). Use the service SDK — never raw HTTP requests to the email API.'
AI generates nodemailer with raw SMTP configuration — credentials hardcoded, no retry, no bounce handling, no deliverability monitoring. One email service SDK call replaces 20 lines of SMTP configuration and provides: delivery tracking, bounce handling, analytics, and reputation management.
Rule 3: React Email or MJML for Templates
The rule: 'Use React Email or MJML for email templates — never string concatenation or template literals. Email HTML is different from web HTML: no CSS flexbox, no CSS grid, limited CSS support, tables for layout, inline styles required. React Email provides: React components that render to email-compatible HTML, preview in the browser, and TypeScript types for template props.'
For React Email: 'Define templates as React components: function WelcomeEmail({ name }: { name: string }) { return (<Html><Head /><Body><Container><Heading>Welcome, {name}</Heading><Text>Thanks for signing up.</Text><Button href="https://app.example.com">Get Started</Button></Container></Body></Html>); }. Import components from @react-email/components — they render to email-compatible HTML.'
For MJML: 'MJML is an email markup language that compiles to responsive HTML: <mj-section><mj-column><mj-text>Welcome</mj-text><mj-button href="...">Get Started</mj-button></mj-column></mj-section>. MJML handles: responsive layout, email client compatibility, and inline style generation. Use when React is not in your stack.'
- React Email: React components → email HTML — TypeScript props, browser preview
- MJML: email markup → responsive HTML — handles compatibility automatically
- Never string concatenation — email HTML needs tables, inline styles, no flexbox
- @react-email/components: Html, Body, Container, Heading, Text, Button, Image
- Preview with npx react-email dev — see templates in browser before sending
React Email renders React components to email-compatible HTML. <Button href='...'>Get Started</Button> becomes a table-based, inline-styled button that works in Gmail, Outlook, and Apple Mail. Preview in your browser before sending.
Rule 4: Deliverability Basics — SPF, DKIM, DMARC
The rule: 'Configure email authentication DNS records: SPF (which servers can send from your domain), DKIM (cryptographic signature proving the email is from you), and DMARC (what to do with emails that fail SPF/DKIM). Without these, your emails land in spam — regardless of content quality. Your email service provides the DNS records to add — follow their setup guide exactly.'
For the from address: 'Use a custom domain (noreply@yourapp.com), not a free email (yourapp@gmail.com). Use a consistent from address across all emails — changing it hurts reputation. Use reply-to for a different reply address: from: "noreply@app.com", replyTo: "support@app.com". Never use a no-reply address as the only contact — provide an alternative.'
For monitoring: 'Monitor: delivery rate (>98% is healthy), bounce rate (<2%), spam complaint rate (<0.1%), open rate (benchmark varies by industry). Your email service dashboard shows these. If bounce rate spikes, clean your list. If spam complaints increase, review your content and sending frequency.'
Your email content quality does not matter if SPF, DKIM, and DMARC are not configured. Email clients check authentication before content. Your email service provides the DNS records — add them during initial setup.
Rule 5: Common Email Patterns
The rule: 'For verification emails: generate a token (crypto.randomUUID()), store with expiry (24 hours), send a link with the token, verify on click, and delete the token. For password reset: same pattern, shorter expiry (1 hour), invalidate on use. For magic links: same pattern, single-use, 15-minute expiry. Never include the password in the email — only a link with a one-time token.'
For transactional vs marketing: 'Transactional emails (receipts, verification, password reset) are triggered by user actions — always send, never batch. Marketing emails (newsletters, promotions) are sent in bulk — respect unsubscribe, comply with CAN-SPAM/GDPR, and use a separate sending domain to protect transactional deliverability. Never mix transactional and marketing on the same domain — marketing complaints hurt transactional delivery.'
For rate limiting: 'Rate limit email sending per user: max 5 verification emails per hour, max 3 password resets per hour. This prevents: email bombing (attacker triggers thousands of emails to a victim), resource abuse (bots triggering expensive email sends), and reputation damage (sending too many emails too fast triggers spam filters).'
- Verification: token + expiry + link — 24h expiry, single-use
- Password reset: token + short expiry (1h) — invalidate on use
- Magic link: token + very short expiry (15min) — single-use
- Separate domains: transactional vs marketing — protect transactional reputation
- Rate limit per user: 5 verifications/hour, 3 resets/hour — prevent email bombing
Complete Email Rules Template
Consolidated rules for email sending.
- Async via queue: never send in request handler — BullMQ/Celery/SQS with retry
- Transactional service: Resend/SendGrid/Postmark — never raw SMTP
- React Email or MJML for templates — never string concatenation
- SPF + DKIM + DMARC on custom domain — follow email service setup guide
- Verification/reset: token + expiry + single-use link — never password in email
- Separate domains: transactional vs marketing — protect delivery reputation
- Rate limit per user — prevent email bombing and reputation damage
- Monitor: delivery >98%, bounce <2%, spam complaints <0.1%