OAuth2
OAuth2 lets your application use short‑lived access tokens instead of passwords. Tokens are scoped, revocable, and regenerable, so a leaked token causes far less harm than a leaked password.
Managing OAuth2 app credentials is painful. Let EmailEngine handle them for you. Once an account is registered with EmailEngine, you can point Nodemailer to EmailEngine and skip all authentication completely. Read more here.
Provider‑agnostic OAuth2 authentication
Use this method when the SMTP server accepts a plain username + access token pair. No client secrets or refresh tokens are involved.
-
auth – authentication object
- type –
'OAuth2'
- user – e‑mail address (required)
- accessToken – access token (required)
- expires – UNIX timestamp when accessToken expires (optional)
- type –
Token scopes • Gmail – request the token with the
https://mail.google.com/
scope • Outlook – request the token with thehttps://outlook.office.com/SMTP.Send
scope
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
user: "user@example.com",
accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x",
},
});
Normal (non‑pooled) transports can override auth per‑message. Create the transport once and pass different tokens in sendMail options as needed.
Gmail‑specific helpers
The sections below cover Gmail‑only flows that Nodemailer can automate for you.
3‑legged OAuth2 authentication
Your app requests consent from the user and receives a refreshToken. Nodemailer uses this token to generate fresh accessTokens when needed.
-
auth – authentication object
- type –
'OAuth2'
- user – e‑mail address (required)
- clientId – OAuth2 client ID (required)
- clientSecret – OAuth2 client secret (required)
- refreshToken – refresh token (required)
- accessToken – access token (optional; Nodemailer auto‑refreshes if missing or expired)
- expires – UNIX expiration timestamp for accessToken (optional)
- accessUrl – custom token endpoint (optional; defaults to Gmail)
- type –
2LO authentication (service accounts)
Use a Google service account to impersonate a user. No interactive consent is required.
-
auth – authentication object
- type –
'OAuth2'
- user – e‑mail address to send as (required)
- serviceClient – service account client_id (required)
- privateKey – service account private key (required)
- type –
Using custom token handling
Register an oauth2_provision_cb
callback that returns a token whenever Nodemailer needs one.
transporter.set("oauth2_provision_cb", (user, renew, cb) => {
const token = userTokens[user];
if (!token) return cb(new Error("Unknown user"));
cb(null, token);
});
Token update notifications
Listen for the token
event to persist newly generated tokens.
transporter.on("token", (t) => {
console.log("User:", t.user);
console.log("New access token:", t.accessToken);
console.log("Expires at:", new Date(t.expires));
});
Examples
- Authenticate with an existing token
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
user: "user@example.com",
accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x",
},
});
- Custom handler – token returned by your own service
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: { type: "OAuth2", user: "user@example.com" },
});
transporter.set("oauth2_provision_cb", (user, renew, cb) => {
cb(null, userTokens[user]);
});
- Full 3‑legged setup – Nodemailer refreshes tokens automatically
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
user: "user@example.com",
clientId: "000000000000-xxx.apps.googleusercontent.com",
clientSecret: "XxxxxXXxX0xxxxxxxx0XXxX0",
refreshToken: "1/XXxXxsss-xxxXXXXXxXxx0XXXxxXXx0x00xxx",
accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x",
expires: 1484314697598,
},
});
- Service account – token re‑generated via 2LO
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
user: "user@example.com",
serviceClient: "113600000000000000000",
privateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...",
accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x",
expires: 1484314697598,
},
});
- Per‑message auth – single transport, many users
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
clientId: "000000000000-xxx.apps.googleusercontent.com",
clientSecret: "XxxxxXXxX0xxxxxxxx0XXxX0",
},
});
transporter.sendMail({
from: "sender@example.com",
to: "recipient@example.com",
subject: "Message",
text: "I hope this message gets through!",
auth: {
user: "user@example.com",
refreshToken: "1/XXxXxsss-xxxXXXXXxXxx0XXXxxXXx0x00xxx",
accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x",
expires: 1484314697598,
},
});
Per‑message auth does not work with pooled transports.
Troubleshooting
- Gmail SMTP requires the
https://mail.google.com/
scope – ensure your token has it. - Gmail API access must be enabled for your Client ID in Google API Manager.