Skip to main content

Resend vs Nodemailer vs Postmark: Email Delivery for Node.js in 2026

·PkgPulse Team

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

PackageWeekly DownloadsApproachSMTP?API?
nodemailer~5.2MSMTP client✅ Any🔌 Via providers
resend~500KHTTP API✅ Native
postmark~95KHTTP 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

FeatureResendNodemailerPostmark
React Email native✅ (render first)✅ (render first)
TypeScript✅ Excellent
SMTP support✅ Any SMTP
Templates✅ ReactHTML/text✅ Dashboard templates
WebhooksN/A (self-manage)✅ Comprehensive
Message streamsN/A✅ (key differentiator)
Attachments✅ Excellent
Batch sending
Free tier3K/monthN/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
SuppressionsManual

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

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 →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.