Source: https://cli.nylas.com/guides/send-email-nodejs

# Send Email from Node.js

Node.js developers have three practical ways to send email in 2026: Nodemailer over SMTP, the Gmail API client library with OAuth 2.0, and a CLI subprocess call that handles auth and delivery in a single shell command. This guide gives you working code for each and a decision matrix to pick the right one.

Written by [Pouya Sanooei](https://cli.nylas.com/authors/pouya-sanooei) Software Engineer

Updated May 23, 2026

> **TL;DR:** Nodemailer works for quick Gmail scripts if you have an app password (5+ min setup). The Gmail API handles production Gmail-only workloads with OAuth 2.0 (15+ min setup). A CLI subprocess call via `child_process` covers 6 providers in 10 lines of JavaScript with no SMTP or SDK dependency.

> **Disclosure:** Nylas CLI is built by Nylas, Inc. This comparison reflects our testing and product understanding as of May 23, 2026.

Command references used in this guide: [`nylas email send`](https://cli.nylas.com/docs/commands/email-send) for the subprocess pattern, [`nylas auth login`](https://cli.nylas.com/docs/commands/auth-login) for OAuth authentication, and [`nylas auth config`](https://cli.nylas.com/docs/commands/auth-config) for headless API-key setup.

## Why send email from Node.js?

Sending email from Node.js means triggering messages from a backend service, script, or serverless function without a separate mail transfer agent. The npm registry lists 4,800+ packages tagged “email” as of May 2026, but the vast majority wrap one of two protocols: SMTP or a provider-specific REST API. Choosing the right approach up front saves you from rewriting auth logic later.

Nodemailer connects directly to an SMTP server, which works for any provider that exposes port 587 or 465. The Gmail API replaces SMTP with REST calls but locks you into Google. A subprocess call delegates the entire problem to a CLI binary that already handles OAuth token refresh, provider differences, and MIME encoding. Each method fits a different stage of a project.

## How do you send email with Nodemailer?

Nodemailer is the most popular Node.js email library, with over 12 million weekly npm downloads as of May 2026. It speaks SMTP natively, so it works with Gmail, Outlook, Yahoo, and any server that accepts authenticated SMTP connections. Setup takes about 5 minutes for Gmail: enable 2-Step Verification, generate a 16-character app password, and install the `nodemailer` package.

The code below connects to Gmail's SMTP server on port 587 with STARTTLS, authenticates with an app password, and sends a single message. Google caps personal Gmail at 500 messages per day. Workspace accounts can send 2,000. If you need Outlook support, swap the host to `smtp.office365.com` and use Modern Auth credentials instead.

```javascript
const nodemailer = require("nodemailer");

const transporter = nodemailer.createTransport({
  host: "smtp.gmail.com",
  port: 587,
  secure: false, // STARTTLS upgrades the connection
  auth: {
    user: "you@gmail.com",
    pass: "your-16-char-app-password",
  },
});

async function sendEmail() {
  const info = await transporter.sendMail({
    from: '"Your Name" <you@gmail.com>',
    to: "recipient@example.com",
    subject: "Quarterly report",
    text: "See the attached spreadsheet.",
    html: "<p>See the attached spreadsheet.</p>",
  });
  console.log("Message sent:", info.messageId);
}

sendEmail();
```

The main tradeoff: SMTP credentials are provider-specific. Switching from Gmail to Outlook requires changing the host, port, and auth method. Google also requires an [app-specific password](https://support.google.com/accounts/answer/185833) since Google retired Less Secure Apps access in September 2024, so Nodemailer now requires a 16-character app password generated with 2-Step Verification enabled. Nodemailer's [SMTP transport docs](https://nodemailer.com/smtp/) cover OAuth2 configuration, but production apps typically need a wrapper to re-authenticate when the token expires every 3,600 seconds.

## How do you send email with the Gmail API client?

The Gmail API uses REST over HTTPS instead of SMTP. Google's official `googleapis` npm package handles OAuth 2.0 token exchange, so you don't manage raw SMTP connections. Setup takes 15+ minutes because you need a Google Cloud project, OAuth consent screen, client credentials file, and at least the `gmail.send` scope. The upside is tighter access control and no app passwords.

The example below uses `@google-cloud/local-auth` to open a browser for the OAuth consent flow, then constructs a raw RFC 2822 message and base64url-encodes it for the `messages.send` endpoint. Google's API charges 100 quota units per `messages.send` call against a daily budget that defaults to 80,000,000 units per project.

```javascript
const { google } = require("googleapis");
const { authenticate } = require("@google-cloud/local-auth");
const path = require("path");

async function sendGmailApi() {
  const auth = await authenticate({
    keyfilePath: path.join(__dirname, "credentials.json"),
    scopes: ["https://www.googleapis.com/auth/gmail.send"],
  });

  const gmail = google.gmail({ version: "v1", auth });

  const message = [
    "To: recipient@example.com",
    "Subject: Quarterly report",
    "Content-Type: text/plain; charset=utf-8",
    "",
    "See the attached spreadsheet.",
  ].join("\n");

  const encoded = Buffer.from(message)
    .toString("base64")
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");

  const res = await gmail.users.messages.send({
    userId: "me",
    requestBody: { raw: encoded },
  });
  console.log("Sent:", res.data.id);
}

sendGmailApi();
```

The Gmail API is Gmail-only. Sending from an Outlook or Yahoo account requires a completely different SDK (Microsoft Graph or Yahoo's IMAP). The [base64url encoding step](https://developers.google.com/gmail/api/reference/rest/v1/users.messages/send) is also easy to get wrong — one missing character replacement and the API returns a 400 error. For Gmail-heavy production systems, the tradeoff is worth it because OAuth 2.0 scopes let you grant only `gmail.send` without exposing the full inbox.

## How do you send email with a CLI subprocess?

A subprocess call offloads SMTP configuration, OAuth 2.0 token management, and MIME encoding to an external binary. Node.js's built-in `child_process` module runs a shell command and returns stdout. The CLI handles authentication for Gmail, Outlook, Exchange, Yahoo, iCloud, and IMAP from a single command, so the Node.js code doesn't import any email library. Setup takes about 2 minutes: install the binary and authenticate once.

The example below sends an email in 10 lines of JavaScript. No `npm install nodemailer`, no `credentials.json` file, no base64url encoding. The `--json` flag returns structured output you can parse with `JSON.parse`. Error handling checks the exit code and stderr for authentication failures or network errors.

```javascript
const { execFileSync } = require("child_process");

function sendEmail(to, subject, body) {
  const result = execFileSync("nylas", [
    "email", "send",
    "--to", to,
    "--subject", subject,
    "--body", body,
    "--json",
  ], { encoding: "utf-8" });

  return JSON.parse(result);
}

// Send a single email
const response = sendEmail(
  "recipient@example.com",
  "Quarterly report",
  "See the attached spreadsheet."
);
console.log("Sent:", response.id);
```

The subprocess pattern works in serverless functions (AWS Lambda, Vercel), CI/CD pipelines, and cron-triggered scripts. The binary is a static Go executable, so there's no native module compilation. The tradeoff is that each call spawns a child process, which adds ~100ms of overhead compared to an in-process SMTP connection. For batch sends over 1,000 messages, Nodemailer's connection pooling is faster.

## How do the three methods compare?

The table below shows the key differences across 7 dimensions. Setup time assumes a Gmail account with 2-Step Verification enabled. Lines of code counts the minimum to send a single plaintext message, including imports and authentication. Provider coverage refers to native support without switching libraries.

| Dimension | Nodemailer SMTP | Gmail API | CLI Subprocess |
| --- | --- | --- | --- |
| Setup time | ~5 minutes | ~15 minutes | ~2 minutes |
| Auth method | App password / SMTP creds | OAuth 2.0 with scopes | OAuth 2.0 (browser flow) |
| Providers | Any SMTP server | Gmail only | Gmail, Outlook, Exchange, Yahoo, iCloud, IMAP |
| Lines of code | ~20 | ~30 | ~10 |
| Dependencies | nodemailer (1 package) | googleapis + local-auth (2 packages) | None (uses child_process) |
| Token refresh | Manual (app passwords don't expire) | SDK handles it | CLI handles it |
| Batch performance | Connection pooling (fast) | HTTP/2 multiplexing | Process-per-send (~100ms overhead) |

## Which method should you choose?

Pick Nodemailer if you're sending from a single SMTP provider, already have app passwords, and want connection pooling for high-volume sends. Its 12M weekly npm downloads mean you'll find answers to most questions on Stack Overflow. Pick the Gmail API if you need scoped OAuth permissions (e.g., `gmail.send` without `gmail.readonly`) and your app is Gmail-only.

- **Quick scripts and prototypes:** CLI subprocess. Zero dependencies, 10 lines of code, and it works across 6 providers without changing a line.
- **Production Gmail app:** Gmail API. Scoped permissions, audit logging in Google Admin Console, and no SMTP passwords to rotate.
- **High-volume transactional email:** Nodemailer with SMTP connection pooling. Reusing a TCP connection across 1,000+ messages is faster than spawning 1,000 subprocesses.
- **Multi-provider support:** CLI subprocess. Switching from Gmail to Outlook requires zero code changes — the binary handles auth and protocol differences.

All three methods work in CI/CD pipelines and serverless environments. The subprocess approach is the fastest to get running because there's no package installation and no credential file to manage. See the [Python equivalent guide](https://cli.nylas.com/guides/send-email-python) for the same comparison in Python, where `smtplib` fills the role that Nodemailer plays here.

## Next steps

- [Send email from Python](https://cli.nylas.com/guides/send-email-python) — same 3-method comparison for Python scripts
- [Gmail SMTP settings](https://cli.nylas.com/guides/gmail-smtp-settings) — port numbers, TLS modes, app password setup, and sending limits
- [Send email from terminal](https://cli.nylas.com/guides/send-email-from-terminal) — send without any programming language
- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented
