Using Deno
Nodemailer runs in Deno 2 without changes. Deno loads npm packages natively and implements the Node.js modules Nodemailer is built on (node:net, node:tls, node:crypto, node:dns, node:child_process), so every bundled transport works the same way it does in Node.js: SMTP with STARTTLS or implicit TLS, pooled SMTP, DKIM signing, OAuth2, Sendmail, SES, and the stream and JSON transports.
The TypeScript definitions bundled with the package are picked up automatically, so deno check type-checks your message objects and transport options.
Importing Nodemailer
Import the package with an npm: specifier:
import nodemailer from "npm:nodemailer";
Or add it to deno.json once and import it by its bare name everywhere else:
deno add npm:nodemailer
import nodemailer from "nodemailer";
Deep imports such as nodemailer/lib/mail-composer work the same way. Deno projects use ES modules, so the examples on the rest of this site apply as shown in their ESM tab.
Sending a message
import nodemailer from "npm:nodemailer";
const transporter = nodemailer.createTransport({
host: "smtp.example.com",
port: 587,
auth: {
user: Deno.env.get("SMTP_USER"),
pass: Deno.env.get("SMTP_PASS"),
},
});
const info = await transporter.sendMail({
from: '"Example App" <app@example.com>',
to: "user@example.com",
subject: "Hello from Deno",
text: "This message was sent with Nodemailer running in Deno.",
});
console.log("Message sent:", info.messageId);
Run it with network access, plus access to the two environment variables the script reads:
deno run --allow-net --allow-env=SMTP_USER,SMTP_PASS send.ts
Grant --allow-net without a host list. Nodemailer resolves the SMTP hostname itself and connects to the resulting IP address, and Deno checks that IP address against the list, so --allow-net=smtp.example.com fails with a permission error. Listing the server's IP addresses next to its hostname works, but breaks as soon as the provider's DNS records change.
Permissions
Deno denies access to the network, the file system, and the environment unless you grant it. Nodemailer only touches what the features you use require:
| Feature | Permission |
|---|---|
| SMTP, SES, and attachments loaded from a URL | --allow-net |
Attachments, DKIM keys, or other content loaded with path from a local file | --allow-read |
DKIM signing with cacheDir set for large messages | --allow-read and --allow-write |
| Sendmail transport | --allow-run (for example --allow-run=/usr/sbin/sendmail) and --allow-env, because Deno passes the full environment to the child process |
createTestAccount() and getTestMessageUrl() | --allow-env (reads the optional ETHEREAL_* variables) |
| Machine hostname for the SMTP greeting, network interface lookup | --allow-sys (optional, see below) |
Without --allow-sys, Nodemailer cannot read the machine hostname and greets the SMTP server with EHLO [127.0.0.1]. Most servers accept that, but some spam filters score it, so either grant --allow-sys=hostname,networkInterfaces or set the name option to your host's fully qualified domain name. Without access to the network interface list, Nodemailer cannot tell whether the machine has IPv6 connectivity and considers both IPv4 and IPv6 addresses of the SMTP server.
When you run a script from a terminal without the flags, Deno prompts for each permission as Nodemailer first needs it. Without a terminal, as in a service or a CI job, Deno does not prompt and the operation fails instead, so pass the flags explicitly there.
Nodemailer 10.0.16 and earlier read the ETHEREAL_* environment variables and the network interface list as soon as the module is imported, so those versions need --allow-env even when Ethereal is never used, and Deno asks for --allow-sys at import time. Later versions read both only when a feature needs them.
Differences from Node.js
Sending works identically, but a few error details come from Deno's own implementation of the Node.js modules and differ slightly:
- System error codes can differ. For example, binding to a
localAddressthat the machine does not have fails withEINVALin Deno andEADDRNOTAVAILin Node.js. The Nodemailer error code (err.code, such asESOCKET) is the same in both, so check that rather than the system error inerr.message. - Invalid URLs rejected by Deno's
URLparser throw aTypeErrorwithout theERR_INVALID_URLcode that Node.js sets.
See also
- Testing with Ethereal - try the examples without sending real email.
- SMTP transport - every connection option.
- Deno permissions - the full reference for the flags above.