TL;DR
Resend is the modern choice for 2026 — excellent TypeScript SDK, native React Email integration, and developer-first API that abstracts deliverability. Nodemailer is the universal SMTP client — use it when you control the SMTP server (AWS SES, Gmail, Mailgun) or need custom transport. Postmark is the specialist for transactional deliverability — message streams, template management, and the best inbox placement rates.
Key Takeaways
- Resend: ~500K weekly downloads — modern API, React Email native, generous free tier (3K/month)
- Nodemailer: ~5.2M weekly downloads — SMTP client, works with any provider, no-lock-in
- Postmark: ~95K weekly downloads — highest deliverability focus, message streams, template engine
- Resend is the fastest path to sending email in a Next.js or React app
- Nodemailer is for self-managed SMTP or when you're already paying for AWS SES/Mailgun
- Postmark wins on deliverability for high-volume transactional senders
- React Email (
@react-email/components) works with all three — render to HTML first
Download Trends
| Package | Weekly Downloads | Approach | SMTP? | API? |
|---|---|---|---|---|
nodemailer | ~5.2M | SMTP client | ✅ Any | 🔌 Via providers |
resend | ~500K | HTTP API | ❌ | ✅ Native |
postmark | ~95K | HTTP API | ❌ | ✅ Native |
Resend
Resend launched in 2023 and became the default email choice for the Next.js/Vercel ecosystem quickly. It's designed for developers who want to send email without thinking about deliverability infrastructure.
import { Resend } from "resend"
const resend = new Resend(process.env.RESEND_API_KEY!)
// Simple email:
const { data, error } = await resend.emails.send({
from: "PkgPulse <noreply@pkgpulse.com>",
to: ["user@example.com"],
subject: "Your weekly package report",
html: "<p>Here are your top packages this week...</p>",
})
if (error) {
console.error("Failed to send:", error)
return
}
console.log("Sent:", data?.id)
Resend with React Email (the killer combo):
// emails/weekly-report.tsx — React component as email template:
import {
Html,
Head,
Body,
Container,
Section,
Text,
Heading,
Link,
Hr,
} from "@react-email/components"
interface WeeklyReportProps {
username: string
packages: Array<{ name: string; downloads: number; trend: "up" | "down" }>
}
export function WeeklyReportEmail({ username, packages }: WeeklyReportProps) {
return (
<Html>
<Head />
<Body style={{ fontFamily: "system-ui, sans-serif", background: "#f9fafb" }}>
<Container style={{ maxWidth: "600px", margin: "0 auto", padding: "24px" }}>
<Heading style={{ fontSize: "24px", color: "#111827" }}>
Your Weekly Package Report
</Heading>
<Text style={{ color: "#6b7280" }}>
Hey {username}! Here are your tracked packages this week:
</Text>
{packages.map((pkg) => (
<Section
key={pkg.name}
style={{
padding: "16px",
background: "#fff",
border: "1px solid #e5e7eb",
borderRadius: "8px",
marginBottom: "12px",
}}
>
<Text style={{ margin: 0, fontWeight: "bold" }}>
<Link href={`https://pkgpulse.com/package/${pkg.name}`}>
{pkg.name}
</Link>{" "}
{pkg.trend === "up" ? "📈" : "📉"}
</Text>
<Text style={{ margin: "4px 0 0 0", color: "#6b7280", fontSize: "14px" }}>
{pkg.downloads.toLocaleString()} weekly downloads
</Text>
</Section>
))}
<Hr style={{ borderColor: "#e5e7eb" }} />
<Text style={{ fontSize: "12px", color: "#9ca3af" }}>
<Link href="https://pkgpulse.com/unsubscribe">Unsubscribe</Link>
</Text>
</Container>
</Body>
</Html>
)
}
// Send the React email via Resend:
import { render } from "@react-email/render"
import { WeeklyReportEmail } from "@/emails/weekly-report"
import { Resend } from "resend"
const resend = new Resend(process.env.RESEND_API_KEY!)
// Option 1: Pass component directly (Resend supports this natively):
await resend.emails.send({
from: "PkgPulse <noreply@pkgpulse.com>",
to: [user.email],
subject: "Your Weekly Package Report",
react: WeeklyReportEmail({ username: user.name, packages: userPackages }),
})
// Option 2: Render to HTML manually:
const html = await render(WeeklyReportEmail({ username: user.name, packages: userPackages }))
await resend.emails.send({
from: "PkgPulse <noreply@pkgpulse.com>",
to: [user.email],
subject: "Your Weekly Package Report",
html,
})
Resend in Next.js App Router:
// app/api/send-welcome/route.ts
import { Resend } from "resend"
import { NextRequest, NextResponse } from "next/server"
import { WelcomeEmail } from "@/emails/welcome"
const resend = new Resend(process.env.RESEND_API_KEY!)
export async function POST(req: NextRequest) {
const { email, name } = await req.json()
const { data, error } = await resend.emails.send({
from: "PkgPulse <welcome@pkgpulse.com>",
to: [email],
subject: "Welcome to PkgPulse!",
react: WelcomeEmail({ name }),
})
if (error) {
return NextResponse.json({ error }, { status: 400 })
}
return NextResponse.json({ id: data?.id })
}
Batch sending with Resend:
// Send to multiple recipients in one API call:
const { data, error } = await resend.batch.send([
{
from: "PkgPulse <noreply@pkgpulse.com>",
to: "alice@example.com",
subject: "Weekly report",
react: WeeklyReportEmail({ username: "Alice", packages: alicePackages }),
},
{
from: "PkgPulse <noreply@pkgpulse.com>",
to: "bob@example.com",
subject: "Weekly report",
react: WeeklyReportEmail({ username: "Bob", packages: bobPackages }),
},
])
Nodemailer
Nodemailer is the universal Node.js SMTP client — it connects to any SMTP server and handles transport, attachments, and email formatting.
import nodemailer from "nodemailer"
// Create a transporter (SMTP):
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST, // "smtp.gmail.com", "email-smtp.us-east-1.amazonaws.com", etc.
port: 587, // 587 (STARTTLS) or 465 (SSL)
secure: false, // true for port 465
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
})
// Send an email:
const info = await transporter.sendMail({
from: '"PkgPulse" <noreply@pkgpulse.com>',
to: "user@example.com",
subject: "Your weekly package report",
text: "Plain text fallback...",
html: "<p>Weekly report HTML...</p>",
})
console.log("Message sent:", info.messageId)
Nodemailer with AWS SES (cost-effective at scale):
import nodemailer from "nodemailer"
import aws from "aws-sdk"
// SES transport via nodemailer-ses-transport or @aws-sdk/client-ses:
const transporter = nodemailer.createTransport({
SES: new aws.SES({
region: "us-east-1",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
}),
sendingRate: 14, // Max 14 messages/second on SES (default quota)
})
await transporter.sendMail({
from: "noreply@pkgpulse.com",
to: "user@example.com",
subject: "Report",
html: "<p>...</p>",
})
Nodemailer with React Email:
import { render } from "@react-email/render"
import { WeeklyReportEmail } from "@/emails/weekly-report"
import nodemailer from "nodemailer"
const transporter = nodemailer.createTransport({ /* your SMTP config */ })
const html = await render(WeeklyReportEmail({ username: "Alice", packages: packages }))
const text = await render(WeeklyReportEmail({ username: "Alice", packages: packages }), { plainText: true })
await transporter.sendMail({
from: "noreply@pkgpulse.com",
to: "alice@example.com",
subject: "Weekly Report",
html,
text, // Plain text fallback for email clients that don't render HTML
})
Nodemailer attachments:
await transporter.sendMail({
from: "noreply@pkgpulse.com",
to: "user@example.com",
subject: "Your package report PDF",
html: "<p>See attached report.</p>",
attachments: [
{
filename: "package-report.pdf",
content: pdfBuffer, // Buffer or Stream
contentType: "application/pdf",
},
{
filename: "data.csv",
path: "/tmp/export.csv", // File path
},
{
filename: "logo.png",
path: "https://pkgpulse.com/logo.png", // URL (Nodemailer downloads it)
cid: "logo@pkgpulse", // CID for inline embedding in HTML
},
],
})
Testing with Nodemailer Ethereal (fake SMTP):
// Create test account (no real emails sent):
const testAccount = await nodemailer.createTestAccount()
const transporter = nodemailer.createTransport({
host: "smtp.ethereal.email",
port: 587,
auth: {
user: testAccount.user,
pass: testAccount.pass,
},
})
const info = await transporter.sendMail({ /* ... */ })
// View in browser:
console.log("Preview:", nodemailer.getTestMessageUrl(info))
Postmark
Postmark is purpose-built for transactional email — it prioritizes inbox placement above everything else.
import postmark from "postmark"
const client = new postmark.ServerClient(process.env.POSTMARK_SERVER_TOKEN!)
// Simple email via API:
await client.sendEmail({
From: "noreply@pkgpulse.com",
To: "user@example.com",
Subject: "Your weekly report",
HtmlBody: "<p>...</p>",
TextBody: "Plain text fallback",
MessageStream: "outbound", // "outbound" or "broadcasts"
})
// Template-based email (stored in Postmark dashboard):
await client.sendEmailWithTemplate({
From: "noreply@pkgpulse.com",
To: "user@example.com",
TemplateAlias: "weekly-report",
TemplateModel: {
username: "Alice",
packages: userPackages,
unsubscribe_url: `https://pkgpulse.com/unsubscribe?token=${token}`,
},
})
Postmark's message streams:
// Transactional stream (default — best deliverability):
await client.sendEmail({
From: "noreply@pkgpulse.com",
To: user.email,
Subject: "Password reset",
HtmlBody: resetEmailHtml,
MessageStream: "outbound", // Transactional
})
// Broadcast stream (for newsletters — separate IP pool):
await client.sendEmail({
From: "newsletter@pkgpulse.com",
To: subscriber.email,
Subject: "PkgPulse Monthly Digest",
HtmlBody: newsletterHtml,
MessageStream: "newsletter", // Broadcast
})
Message streams keep transactional email (password resets, receipts) completely separated from marketing email — a single spam complaint on your newsletter won't tank your transactional deliverability.
Postmark webhooks for tracking:
// app/api/postmark-webhook/route.ts
export async function POST(req: NextRequest) {
const event = await req.json()
switch (event.Type) {
case "Delivery":
await db.emailLogs.update({ messageId: event.MessageID, status: "delivered" })
break
case "Bounce":
await handleBounce(event.Email, event.Type, event.Description)
break
case "SpamComplaint":
await unsubscribeEmail(event.Email)
break
case "Open":
await db.emailLogs.update({ messageId: event.MessageID, openedAt: new Date() })
break
case "Click":
await trackLinkClick(event.MessageID, event.OriginalLink)
break
}
return NextResponse.json({ ok: true })
}
Feature Comparison
| Feature | Resend | Nodemailer | Postmark |
|---|---|---|---|
| React Email native | ✅ | ✅ (render first) | ✅ (render first) |
| TypeScript | ✅ Excellent | ✅ | ✅ |
| SMTP support | ❌ | ✅ Any SMTP | ❌ |
| Templates | ✅ React | HTML/text | ✅ Dashboard templates |
| Webhooks | ✅ | N/A (self-manage) | ✅ Comprehensive |
| Message streams | ❌ | N/A | ✅ (key differentiator) |
| Attachments | ✅ | ✅ Excellent | ✅ |
| Batch sending | ✅ | ✅ | ✅ |
| Free tier | 3K/month | N/A (pay SMTP) | 100/month |
| Pricing | $20/mo (50K) | Pay SMTP provider | $15/mo (10K) |
| Deliverability focus | ✅ Good | ⚠️ Depends on SMTP | ✅ Best-in-class |
| Open/click tracking | ✅ | ❌ | ✅ |
| Suppressions | ✅ | Manual | ✅ |
When to Use Each
Choose Resend if:
- Building a Next.js / React app and want the cleanest DX
- Using React Email for templates (native integration)
- Starting fresh — best free tier, fastest setup
- You want a modern API over SMTP complexity
Choose Nodemailer if:
- You're already paying for AWS SES, Mailgun, or another SMTP provider
- You need attachment handling (complex, multi-part emails)
- You want zero vendor lock-in for email transport
- You need to test locally without sending real emails (Ethereal SMTP)
Choose Postmark if:
- Deliverability is mission-critical (transactional email, receipts)
- You need separate IP pools for transactional vs marketing
- Template management in a dashboard appeals to non-developer team members
- High-volume sending with detailed delivery analytics
Deliverability Fundamentals and DNS Setup
All three libraries ultimately depend on the same email deliverability infrastructure: proper DNS records (SPF, DKIM, DMARC) and a sending domain with a good reputation. Resend and Postmark manage reputation and infrastructure for you, but you still need to add DNS records to your domain. Resend's onboarding walks you through adding a single DKIM record; Postmark requires SPF, DKIM, and optionally DMARC. With Nodemailer and AWS SES, you must configure and verify the sending domain in SES, set up SPF records pointing to SES's mail servers, and configure DKIM in both SES and your DNS. The extra setup burden of Nodemailer with SES is real, but AWS SES's sending reputation for domains with clean records is excellent because Amazon's IP ranges are heavily whitelisted. Teams migrating from one provider to another should expect a warm-up period — ISPs track the per-IP reputation of email senders, and new IP pools start with no reputation history, which can cause initial delivery to spam folders until the sending volume establishes a positive track record.
Handling Bounces, Complaints, and List Hygiene
In production, every email system needs bounce and complaint handling. Hard bounces (invalid addresses) and spam complaints will damage your sender reputation if you continue sending to those addresses. Resend automatically suppresses bounced and complained-to addresses and surfaces this data through its dashboard and webhooks. Postmark has one of the most comprehensive bounce management systems — it categorizes bounces by type (hard bounce, soft bounce, spam complaint, challenge response) and automatically suppresses addresses, with APIs to query suppression lists and remove addresses from them. With Nodemailer, bounce handling is entirely your responsibility: you need to process bounce notification emails from your SMTP provider (AWS SES uses SNS topics for this), parse them, and maintain your own suppression list. The automation Resend and Postmark provide around list hygiene is one of the most compelling reasons to choose a managed email API over raw SMTP for transactional email.
Testing Strategies and Local Development
Email testing is an area where the developer experience diverges significantly. Nodemailer's Ethereal fake SMTP account is the most convenient local testing story — create a test account, send emails through it, and view rendered emails at the Ethereal web URL. No real emails are sent and the preview URL works for any team member with access. Resend provides a test mode using re_test_XXXX API keys that accept calls without sending real emails, and their dashboard shows what would have been sent. Postmark has a dedicated sandbox server mode accessible via separate API token. For automated testing in CI pipelines, Nodemailer's Ethereal is the most reliable choice since it requires no external API key and works without network access to third-party services. For integration tests that validate the full email sending path including template rendering, Resend's test mode is convenient in a Next.js/Vercel environment where the API key can be set per-environment.
Pricing at Scale and Total Cost Analysis
The economics shift significantly as sending volume grows. Resend's pricing tiers at roughly $0.40 per 1000 emails after the free tier (3,000/month). Postmark starts at $15/month for 10,000 emails, or about $1.50 per 1,000 — more expensive per-email but with superior deliverability infrastructure for transactional mail. AWS SES via Nodemailer charges $0.10 per 1,000 emails — four times cheaper than Resend and fifteen times cheaper than Postmark — but requires your own domain warm-up, bounce handling infrastructure, and ongoing maintenance. At 500,000 emails per month: SES costs roughly $50, Resend roughly $200, and Postmark roughly $750. For a startup sending confirmation emails and password resets at that volume, SES's cost advantage is significant enough to justify the infrastructure investment. For a team focused on product development rather than email infrastructure, Resend's developer experience and managed deliverability often justify the premium.
Choosing Based on Team Composition and Growth Stage
The right email library often depends as much on team composition as on technical requirements. Resend's developer-first API and clean documentation make it the fastest path from zero to working transactional email — a solo developer or small team can have email sending working in under an hour. Its free tier (3,000 emails per month) is sufficient for early-stage products, and upgrading is a straightforward plan change. Postmark is better suited for teams with a dedicated product or growth person who needs visibility into email metrics — its dashboard surfaces delivery rates, open rates, and bounce information in a way that non-developers can act on. Nodemailer with AWS SES makes most economic sense for companies with existing AWS infrastructure, where SES's low per-email cost and deep IAM integration fit naturally into the existing operational model. At the enterprise scale where millions of emails per month are common, SES's cost advantage compounds significantly enough to justify the infrastructure investment.
Methodology
Download data from npm registry (weekly average, February 2026). Pricing reflects public plans as of March 2026. Feature comparison based on Resend SDK v4.x, Nodemailer v6.x, and Postmark SDK v4.x documentation.
Compare email and notification package ecosystems on PkgPulse →
Compare Nodemailer and Resend package health on PkgPulse.
See also: cac vs meow vs arg 2026 and Ink vs @clack/prompts vs Enquirer, acorn vs @babel/parser vs espree.