NodemailerTypeScriptSMTP
A Nodemailer Alternative for TypeScript Apps
When to use Nodemailer, when to use a provider API, and why Email SDK keeps SMTP as one route instead of the whole abstraction.
June 10, 20266 min read
Nodemailer is still the right answer for plenty of apps. If you need direct SMTP control, custom transport behavior, or you are maintaining an older Node.js service, it is mature and battle-tested. The question is not whether Nodemailer is good. The question is whether your app wants SMTP to be the whole email layer.
Most new TypeScript products do not stop at SMTP. They start with Resend or Postmark, add SendGrid because a customer already has it, keep SES around for cost or AWS policy, then discover that every provider wants a slightly different payload. That is the problem Email SDK is built around.
Nodemailer vs Email SDK
| Need | Nodemailer | Email SDK |
|---|---|---|
| SMTP transport | Excellent direct SMTP support. | SMTP adapter without making SMTP the only path. |
| Provider APIs | Usually handled outside Nodemailer or through custom transport work. | Dedicated adapters for Resend, Postmark, SendGrid, Mailgun, SES, and more. |
| Fallback routing | You own the routing, retries, and failure policy. | Fallback adapters and retries are part of the client configuration. |
| TypeScript message shape | Typed, but centered on Nodemailer's transport model. | One normalized message shape with provider-aware validation. |
| Unsupported fields | Depends on your transport and app code. | Limited adapters reject fields they cannot safely send. |
Use Nodemailer when
- SMTP is your only transport and you want direct control over it.
- You already have a stable Nodemailer setup and no provider-switching pain.
- Your app depends on custom transport behavior that should stay close to SMTP.
Use Email SDK when
- You want one TypeScript email API across SMTP and provider APIs.
- You need fallback routes without copying provider-specific code through the app.
- You want unsupported fields to fail before a provider silently drops them.
- You expect to support more than one customer, tenant, or email provider over time.
import { createEmailClient } from "@opencoredev/email-sdk";
import { postmark } from "@opencoredev/email-sdk/postmark";
import { smtp } from "@opencoredev/email-sdk/smtp";
const email = createEmailClient({
adapters: [
postmark({ serverToken: process.env.POSTMARK_SERVER_TOKEN! }),
smtp({
host: process.env.SMTP_HOST!,
auth: {
user: process.env.SMTP_USER!,
pass: process.env.SMTP_PASS!,
},
}),
],
fallback: ["smtp"],
});
await email.send({
from: "Acme <hello@acme.com>",
to: "user@example.com",
subject: "Reset your password",
text: "Use this link to reset your password.",
});