From atum-stack-backend
Transactional email delivery pattern library for backend services — Resend (developer-friendly, React Email integration), Postmark (best deliverability, fast support), SendGrid (enterprise, complex but powerful), Mailgun (API-first, good EU compliance), Amazon SES (cheapest at scale, requires reputation management), Brevo (formerly Sendinblue, EU-based), Loops (product-led growth focus). Covers domain authentication (SPF, DKIM, DMARC), dedicated IPs vs shared pools, bounce and complaint handling, suppression lists, webhook events (delivered / opened / clicked / bounced / complained), rate limiting, retry strategies with exponential backoff, template management (inline vs stored templates), and test email flows. Use when sending transactional emails from a backend: password resets, order confirmations, notifications, welcome emails, magic links, invoices. Differentiates from marketing email (newsletters via Ghost, Mailchimp, ConvertKit) by focusing on 1-to-1 triggered emails with deliverability-critical SLAs.
npx claudepluginhub arnwaldn/atum-plugins-collection --plugin atum-stack-backendThis skill uses the workspace's default tool permissions.
Patterns pour envoyer des emails transactionnels depuis un backend (password reset, order confirmation, magic link, notification, etc.).
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides implementation of event-driven hooks in Claude Code plugins using prompt-based validation and bash commands for PreToolUse, Stop, and session events.
Patterns pour envoyer des emails transactionnels depuis un backend (password reset, order confirmation, magic link, notification, etc.).
Transactional ≠ marketing :
Ce skill couvre uniquement le transactional. Pour du marketing, voir shopify-expert (campaigns e-commerce), ghost-expert (newsletters payantes), ou outils externes (Mailchimp, Klaviyo).
Budget serré + besoin simple ?
├── < 3000 emails/mo → Resend (gratuit, React Email natif)
├── > 10k emails/mo → Amazon SES ($0.10/1000)
└── Exigence deliverability max → Postmark (premium)
Client EU avec contraintes GDPR ?
├── Brevo (France, data center EU)
├── Mailgun (EU region sélectionnable)
└── Self-hosted Postfix (dernier recours)
Volume élevé + SLA critique ?
├── SendGrid Pro (enterprise)
└── Postmark
Stack Next.js / React ?
└── Resend + React Email (DX imbattable)
npm install resend
// lib/email.ts
import { Resend } from 'resend'
const resend = new Resend(process.env.RESEND_API_KEY!)
export async function sendEmail({
to,
subject,
react,
}: {
to: string
subject: string
react: React.ReactElement
}) {
const { data, error } = await resend.emails.send({
from: 'MyApp <no-reply@example.com>',
to,
subject,
react,
})
if (error) {
throw new Error(`Email send failed: ${error.message}`)
}
return data
}
npm install @react-email/components
// emails/WelcomeEmail.tsx
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Preview,
Text,
} from '@react-email/components'
export function WelcomeEmail({ firstName }: { firstName: string }) {
return (
<Html>
<Head />
<Preview>Welcome to MyApp, {firstName}!</Preview>
<Body style={{ fontFamily: 'sans-serif', backgroundColor: '#f6f6f6' }}>
<Container style={{ backgroundColor: '#fff', padding: '40px' }}>
<Heading>Welcome, {firstName}!</Heading>
<Text>Thanks for signing up. Get started by exploring your dashboard.</Text>
<Button
href="https://example.com/dashboard"
style={{ backgroundColor: '#000', color: '#fff', padding: '12px 24px' }}
>
Go to dashboard
</Button>
</Container>
</Body>
</Html>
)
}
// Utilisation
import { WelcomeEmail } from '@/emails/WelcomeEmail'
await sendEmail({
to: 'user@example.com',
subject: 'Welcome!',
react: <WelcomeEmail firstName="Arnaud" />,
})
npx @react-email/cli dev --dir ./emails
# Ouvre un dashboard sur http://localhost:3000
import { ServerClient } from 'postmark'
const client = new ServerClient(process.env.POSTMARK_SERVER_TOKEN!)
await client.sendEmail({
From: 'no-reply@example.com',
To: 'user@example.com',
Subject: 'Password reset',
HtmlBody: '<p>Click <a href="...">here</a> to reset</p>',
TextBody: 'Click here to reset: ...',
MessageStream: 'outbound', // 'outbound' = transactional, 'broadcast' = marketing
Tag: 'password-reset',
TrackOpens: true,
})
Postmark sépare strictement les streams transactional et marketing — impossible d'envoyer du marketing sur un stream transactional.
import sgMail from '@sendgrid/mail'
sgMail.setApiKey(process.env.SENDGRID_API_KEY!)
await sgMail.send({
to: 'user@example.com',
from: { email: 'no-reply@example.com', name: 'MyApp' },
subject: 'Your order confirmation',
html: '<p>Thanks for your order!</p>',
templateId: 'd-abc123', // Dynamic template (recommended)
dynamicTemplateData: {
orderId: 'ORD-123',
total: '€59.99',
},
categories: ['transactional', 'order-confirmation'],
})
import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2'
const client = new SESv2Client({ region: 'eu-west-3' })
await client.send(
new SendEmailCommand({
FromEmailAddress: 'no-reply@example.com',
Destination: { ToAddresses: ['user@example.com'] },
Content: {
Simple: {
Subject: { Data: 'Your invoice' },
Body: {
Html: { Data: '<p>See attached invoice</p>' },
Text: { Data: 'See attached invoice' },
},
},
},
})
)
SES pièges :
Sans ces 3 records, les emails vont en spam.
TXT @ "v=spf1 include:_spf.google.com include:amazonses.com ~all"
Autorise les serveurs listés à envoyer des mails au nom du domaine.
TXT <provider>._domainkey "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."
Le provider fournit la clé publique à mettre en DNS. Tous les providers modernes gèrent ça via leur UI.
TXT _dmarc "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; adkim=s; aspf=s"
Politique : none (monitoring), quarantine (spam folder), reject (bounce).
Règle prod : p=none pendant 2-4 semaines pour collecter les rapports, puis p=quarantine, puis p=reject.
// Next.js webhook handler
export async function POST(request: Request) {
const body = await request.json()
switch (body.type) {
case 'email.bounced':
await markAsBounced(body.data.to)
break
case 'email.complained':
await markAsComplained(body.data.to) // unsubscribe définitif
break
case 'email.delivered':
await updateStatus(body.data.id, 'delivered')
break
}
return new Response('ok')
}
Ne jamais envoyer d'email à une adresse qui a :
Maintenir une table email_suppressions en DB et vérifier avant chaque envoi.
Les emails transactionnels peuvent échouer temporairement (rate limit, provider down). Implémenter un retry avec exponential backoff :
import pRetry from 'p-retry'
await pRetry(
async () => {
try {
await sendEmail({ to, subject, react })
} catch (err) {
if (isRetryable(err)) throw err
throw new pRetry.AbortError(err) // erreur permanente, pas de retry
}
},
{
retries: 5,
factor: 2,
minTimeout: 1000,
maxTimeout: 30000,
}
)
Pour des volumes élevés, passer par une queue (BullMQ, Temporal, Inngest).
Chaque provider a ses limites :
| Provider | Rate limit typique |
|---|---|
| Resend | 10 req/s (plan Free), 100 req/s (Pro) |
| Postmark | 300 req/s |
| SendGrid | 600 req/s (Essentials), 1500 (Pro) |
| SES | 14 emails/s (new accounts), 200+ après reputation |
Implémenter un throttle côté app pour éviter les 429 :
import Bottleneck from 'bottleneck'
const limiter = new Bottleneck({ minTime: 100 }) // 10 req/s max
await limiter.schedule(() => sendEmail({ to, subject, react }))
| Strategy | Avantages | Inconvénients |
|---|---|---|
| Inline (React Email) | DX, versioning Git, preview local | Dev deploy à chaque changement de copy |
| Stored (SendGrid Dynamic, Postmark Templates) | Modif par non-dev, A/B test | Pas de versioning Git natif, drift |
Recommandation : React Email + Resend pour tout projet moderne. Stored templates seulement si le client veut éditer les emails lui-même.
From: no-reply@example.com pour un email qui attend des réponses (support)mail.example.com ou no-reply.example.com)email-templates de atum-stack-webghost-expert ou shopify-expert