Install the library
Nodemailer installs from npm and has no runtime dependencies of its own, which is part of why it became the default choice.
It supports both SMTP and a pluggable transport interface, so the same calling code can target a provider's HTTP API through a transport plugin instead of SMTP.
Implementation
The working sequence is two steps. Create a transporter with the host, port, security flag and authentication object. Call sendMail with an options object holding from, to, subject, and either text or html.
The secure flag is the setting people get wrong. Set to true it opens an implicit TLS connection, which pairs with port 465. The false value instead starts in the clear and upgrades through STARTTLS, which is the port 587 path. Pairing secure: true with port 587 produces a connection that hangs rather than returning a usable error.
Set both text and html when sending HTML. Nodemailer assembles the multipart message, and the plain-text part is what some clients render and what filters treat more favourably.
sendMail returns a promise resolving to an info object containing the message ID and the server's accepted and rejected recipient arrays. A send can resolve successfully while individual recipients were rejected, so checking info.rejected matters when sending to more than one address.
Reuse a transporter across sends rather than creating one per message. Each new transporter opens a fresh connection, and providers limit concurrent connections per account.
Run the script
Running the file executes the send. Because sendMail is asynchronous, an unhandled rejection exits silently in older Node versions, so wrap the call in try/catch or attach a catch handler.
Credentials belong in environment variables rather than the source file. A committed application password is a live sending credential for the mailbox that issued it.
Common provider configuration
Gmail uses smtp.gmail.com on port 587 with STARTTLS or 465 with implicit TLS. Google blocks ordinary account passwords for third-party clients, so an app password generated against an account with two-step verification is required.
Microsoft 365 uses smtp.office365.com on port 587 with STARTTLS, documented in Microsoft's settings reference. Basic authentication on this path is scheduled for deprecation in favour of OAuth, which Nodemailer supports through an OAuth2 auth object.
Both are mailbox providers, and both apply per-mailbox sending limits sized for a person rather than an application. Microsoft documents 10,000 recipients per day and 30 messages per minute on client submission.
For application volume the alternative is a submission service authenticating as your own verified domain, reached either over SMTP with issued credentials or through an HTTP API. Nitrosend's integration surface is a REST API, an MCP server and a CLI, with no published Node SDK, so integration there is an ordinary HTTP request rather than a package install.
The official package, and the split that matters
There is one official package for Node and TypeScript, and it is the exception to the REST-only rule that applies to every other language. It is fully typed, ships with zero runtime dependencies, and requires Node 18 or later.
Zero dependencies is a deliberate property rather than a boast. An email SDK sitting in a dependency tree with a dozen transitive packages is a supply-chain surface attached to a credential that can send as your domain, so the smaller that surface the better.
Most consequential is the split between server and browser entry points. The main export is a server SDK for Node, edge functions or any backend, and it takes a secret key with full account access. The package also publishes a separate browser entry, and the distinction is not cosmetic: a secret key must never reach a browser bundle, and having two entry points is what makes that boundary explicit rather than a matter of discipline.
Worth knowing that raw HTTP is still a first-class option. Our own REST guide's JavaScript examples use plain fetch, and there is nothing second-class about that: the package calls the same API, and the same key authenticates both, so the choice is about typing convenience rather than capability.
None of that split, the typed errors, the zero dependencies, is really the point. The point is you don't have to write any of it. Tell Claude, Cursor, or Codex to send the email, and our MCP server composes it, picks the transport, and sends, with you approving before anything leaves. Email marketing is moving from developers wiring up SDKs to agents doing the wiring, and Nitrosend is the option built for that shift, which is why we're a listed app in the ChatGPT app marketplace rather than just another package on npm.