
The most common comment on my Nodemailer article is some version of “Gmail says invalid login.” The reason is simple: Google stopped letting apps sign in with your regular account password. You need either an app password or OAuth2.
Option 1: App password (fastest)
An app password is a 16-character password Google generates for one app. It only works if 2-Step Verification is on.
- Turn on 2-Step Verification in your Google Account security settings.
- Search for “App passwords” in your Google Account and create one.
- Copy the 16-character password into your .env.
GMAIL_USER=you@gmail.com
GMAIL_APP_PASSWORD=abcdabcdabcdabcd
import nodemailer from "nodemailer";
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.GMAIL_USER,
pass: process.env.GMAIL_APP_PASSWORD,
},
});await transporter.verify(); // fails fast if credentials are wrong
await transporter.sendMail({
from: `"My App" <${process.env.GMAIL_USER}>`,
to: "friend@example.com",
subject: "Hello from Node.js",
text: "It works!",
});If you don’t see the App passwords option, 2-Step Verification is probably off, or your Google Workspace admin has disabled app passwords.
Option 2: OAuth2 (better for production)

OAuth2 uses tokens instead of a password, and you can revoke access without changing anything else.
- In Google Cloud Console, create a project and enable the Gmail API.
- Set up the OAuth consent screen.
- Create an OAuth client ID and note the client ID and client secret.
- Use the OAuth 2.0 Playground with your own client credentials to authorize the https://mail.google.com/ scope and get a refresh token.
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
type: "OAuth2",
user: process.env.GMAIL_USER,
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
refreshToken: process.env.GOOGLE_REFRESH_TOKEN,
},
});Nodemailer uses the refresh token to get fresh access tokens on its own. If your consent screen stays in testing mode, refresh tokens can expire after a short time, so publish the app or plan to renew them.
Common errors

- 535-5.7.8 Username and Password not accepted: you're using your normal password. Use an app password or OAuth2.
- Invalid login with an app password: spaces got copied into the password. Paste it without spaces.
- invalid_grant: the refresh token expired or was revoked. Generate a new one.
- Connection timeout: your host or firewall blocks the port. Try port 465 with secure: true, or check outbound SMTP rules.
Some cloud providers block outbound SMTP ports by default. If it works on your laptop but times out on the server, that’s usually why.
Should you use Gmail at all?
For contact forms, alerts and side projects, Gmail is fine. Personal accounts have daily sending limits, and your messages come from a @gmail.com address. For anything customer-facing, use your own domain with a provider like Resend or AWS SES. I compared them in my 2026 Node.js email guide.
