{"post":{"slug":"how-to-send-emails-from-an-app-2026","title":"How to send emails from an app in 2026: transactional email done right","excerpt":"Sending transactional email in 2026 means choosing the right provider, getting SPF and DKIM right from day one, and never blocking the request thread on delivery.","category":"code","date":"April 2, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">S</span>ending email from an application sounds trivial until your first transactional email lands in spam and you realize the domain reputation is shot. The full setup — provider selection, DNS authentication, async delivery, templating, and bounce handling — takes an afternoon to do right and months to recover from if skipped. Here is the complete guide.\n\n## What changed in 2026\n\n- **Resend** (launched 2023) is now the developer-favorite transactional email provider: clean API, React Email native support, and generous free tier (~3k emails/month).\n- **Google and Yahoo mandated DMARC** for bulk senders starting in 2024; SPF + DKIM + DMARC are now enforced, not optional. Miss them and Gmail routes you to spam.\n- **React Email 3.x** is the dominant template approach — write email templates as React components, preview in a browser, export to HTML.\n- **Postmark** remains the deliverability leader for transactional email (welcome emails, password resets); SendGrid and Mailgun are better for mixed transactional + marketing sends.\n\n## Provider comparison\n\n| Provider | Best for | Free tier | Notable feature |\n| --- | --- | --- | --- |\n| Resend | Devs, React Email native | 3k/month | Clean SDK, React templates |\n| Postmark | Transactional deliverability | 100/month | Dedicated transactional streams |\n| SendGrid | Mixed transactional + marketing | 100/day | Large ecosystem |\n| Mailgun | API-first | 1k/month (3 months) | Europe data residency |\n| AWS SES | AWS-native, high volume | ~1k/month (EC2 free) | Cheapest at scale (~$0.10/1k) |\n\nFor a new app: **Resend** to ship fast, **Postmark** for critical transactional (password reset, billing). Move to **SES** at scale for cost savings.\n\n## Sending with Resend (Node.js)\n\n```ts\nimport { Resend } from 'resend';\nimport { WelcomeEmail } from './emails/WelcomeEmail.js';\n\nconst resend = new Resend(process.env.RESEND_API_KEY!);\n\nexport async function sendWelcomeEmail(user: { email: string; name: string }) {\n  const { data, error } = await resend.emails.send({\n    from: 'ByteLedger <hello@mail.byteledger.app>',\n    to: user.email,\n    subject: 'Welcome to ByteLedger',\n    react: WelcomeEmail({ name: user.name }),\n  });\n\n  if (error) {\n    throw new Error(`Failed to send welcome email: ${error.message}`);\n  }\n\n  return data;\n}\n```\n\n**Call this from a background job, not directly from your route handler.**\n\n## React Email template\n\n```tsx\n// emails/WelcomeEmail.tsx\nimport {\n  Body, Button, Container, Head, Heading, Html, Preview, Text,\n} from '@react-email/components';\n\ninterface Props { name: string; }\n\nexport function WelcomeEmail({ name }: Props) {\n  return (\n    <Html>\n      <Head />\n      <Preview>Welcome to ByteLedger, {name}</Preview>\n      <Body style={{ fontFamily: 'sans-serif', background: '#f4f4f5' }}>\n        <Container style={{ maxWidth: 600, margin: '0 auto', padding: 24 }}>\n          <Heading>Hi {name}, welcome aboard!</Heading>\n          <Text>Your account is ready. Get started below.</Text>\n          <Button href=\"https://app.byteledger.app/dashboard\"\n                  style={{ background: '#2563eb', color: '#fff', padding: '12px 24px' }}>\n            Go to dashboard\n          </Button>\n        </Container>\n      </Body>\n    </Html>\n  );\n}\n```\n\nPreview locally: `npx react-email dev` spins up a local preview server.\n\n## DNS authentication (do this before any sends)\n\n### SPF\nAdd a TXT record to your sending domain:\n```\nTXT mail.yourdomain.com  \"v=spf1 include:_spf.resend.com ~all\"\n```\n\n### DKIM\nYour provider generates a public/private key pair. Add the CNAME or TXT record they provide. Example (Resend):\n```\nCNAME resend1._domainkey.mail.yourdomain.com  → resend1._domainkey.resend.com\nCNAME resend2._domainkey.mail.yourdomain.com  → resend2._domainkey.resend.com\n```\n\n### DMARC\n```\nTXT _dmarc.yourdomain.com  \"v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com\"\n```\n\nStart with `p=none` (monitoring only), confirm reports look clean, then move to `p=quarantine` then `p=reject`.\n\n**Use your provider's domain verification checklist** — they walk through each record and confirm propagation.\n\n## Async delivery pattern\n\n```ts\n// With BullMQ (Node.js)\nimport { Queue } from 'bullmq';\nconst emailQueue = new Queue('emails', { connection: redis });\n\n// In your route handler — fast, non-blocking\nawait emailQueue.add('welcome', { userId: user.id, email: user.email });\n\n// In your worker process — slow, runs in background\nimport { Worker } from 'bullmq';\nnew Worker('emails', async (job) => {\n  if (job.name === 'welcome') {\n    const user = await db.users.findUnique({ where: { id: job.data.userId } });\n    await sendWelcomeEmail(user!);\n  }\n}, { connection: redis });\n```\n\nBackground queues add retry logic automatically — transient provider failures don't lose emails.\n\n## How to handle bounces and complaints\n\n1. **Configure webhooks** from your provider for `bounce` and `complaint` events.\n2. **Suppress bounced addresses immediately** — add to a blocklist; do not retry hard bounces.\n3. **Handle complaints** (spam reports) with unsubscribes — mark the user as opted-out.\n4. **Monitor bounce rate** — keep below 2%; above that, providers throttle or suspend your account.\n\n## Common mistakes\n\n**Sending from a bare domain (`@yourdomain.com`) for transactional email.** Use a subdomain (`@mail.yourdomain.com`) so a deliverability issue doesn't affect your main domain's reputation.\n\n**Awaiting the send inside the request handler.** If the email provider is slow (200ms–1s) or down, your API call hangs or fails. Always enqueue.\n\n**No unsubscribe link in marketing emails.** Required by CAN-SPAM and GDPR; Gmail now enforces it for bulk senders.\n\n**Testing with production sends.** Use your provider's sandbox mode or an inbox testing service (Mailtrap, Mailpit for local dev) to avoid accidental sends to real addresses.\n\n**HTML emails without a plain-text version.** Some clients prefer or require plain text; spam filters check both. Always send multipart.\n\n## What to skip\n\n- **Self-hosted Postfix/Exim** for transactional email in 2026 — IP reputation management, bounce processing, and ISP feedback loops are a full-time job.\n- **Inline styles by hand in HTML emails** — use React Email or MJML which handle the cross-client CSS quirks for you.\n- **Storing email bodies in your database** — log the send event (provider message ID, recipient, timestamp); don't store the full HTML unless you have a legal requirement.\n\n## FAQ\n\n**What is the difference between transactional and marketing email?**\nTransactional = triggered by a user action (password reset, order confirmation). Marketing = sent to a list. Keep them on separate provider streams/domains — a marketing spam complaint should not affect transactional deliverability.\n\n**How do I test emails locally?**\nRun Mailpit (`docker run -d -p 1025:1025 -p 8025:8025 axllent/mailpit`) — it catches all outgoing SMTP and shows them in a web UI at `localhost:8025`.\n\n**How long does DNS propagation take for DKIM?**\nTypically minutes to hours; up to 48 hours worst case. Verify propagation with `dig TXT resend1._domainkey.mail.yourdomain.com` before your first send.\n\n**Can I use the same domain for transactional and marketing?**\nTechnically yes but it is risky — a campaign with high unsubscribes or complaints damages the domain reputation your transactional emails depend on. Use a separate subdomain.\n\n## Where to go next\n\nSee [How to build a REST API in Node in 2026](/blog/how-to-build-a-rest-api-in-node-2026), [How to cache with Redis in 2026](/blog/how-to-cache-with-redis-2026), and [How to set up Supabase in 2026](/blog/how-to-set-up-supabase-2026).","hue":308,"metaTitle":"How to Send Emails from an App 2026: Resend, DKIM, Templates","metaDescription":"Send transactional email from your app in 2026. Covers provider selection, SPF and DKIM setup, async delivery, React Email templates, and bounce handling.","keywords":["how to send emails from an app 2026","transactional email 2026","resend email api tutorial","postmark vs sendgrid 2026","spf dkim dmarc setup","react email template","send email node.js 2026","email deliverability 2026","async email sending","transactional email best practices"],"tldr":["**Use a transactional email provider** — Resend, Postmark, or SendGrid; never send from your own SMTP server or a shared hosting relay.","**Set up SPF, DKIM, and DMARC before sending** — without DNS authentication records, your emails land in spam and domain reputation is hard to recover.","**Send email asynchronously** — enqueue it in a background job; never await the email provider call inside a request handler.","**Verify email addresses before sending** — a bounce rate above 2% risks getting your sending domain flagged or suspended by the provider.","**Skip:** building your own email template engine. Use React Email or MJML for cross-client HTML templates; hand-coded HTML breaks in Outlook consistently."],"readTime":"5 min read","wordCount":1082},"related":[{"slug":"best-email-apis-for-developers-2026","title":"Best email APIs for developers in 2026: Resend vs Postmark vs SendGrid vs Loops","excerpt":"Honest 2026 ranking of transactional email APIs by deliverability, DX, and price. The four worth using and the ones to skip.","category":"code","date":"January 8, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":320},{"slug":"how-to-learn-express-2026","title":"How to learn Express in 2026: the no-nonsense guide","excerpt":"Express remains the backbone of countless Node.js APIs in 2026, even as Hono and Fastify gain ground. Here is how to learn it efficiently and correctly.","category":"code","date":"March 13, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":316},{"slug":"how-to-learn-svelte-2026","title":"How to learn Svelte in 2026: the fast-track guide","excerpt":"Svelte 5 with runes is the most significant rewrite since the framework launched. Here is how to learn it correctly and avoid the outdated tutorials.","category":"code","date":"February 25, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":319}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-seed-test-data-2026","title":"How to seed test data in 2026","excerpt":"Seeding test data deterministically keeps tests fast, isolated, and reproducible. This guide covers factory patterns, Faker.js, Prisma seeds, and database snapshots in 2026.","category":"code","date":"April 2, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":314},"older":{"slug":"how-to-learn-to-code-with-ai-2026","title":"How to Learn to Code With AI in 2026: An Honest Guide","excerpt":"AI can make learning to code faster or hollow out your skills entirely. Here is the honest 2026 path, the tools worth using, and the traps to avoid.","category":"ai","date":"April 1, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":280}}