# Nodemailer “Missing credentials for "PLAIN"”: the fix

Nodemailer throws Missing credentials for "PLAIN" before it asks the server anything: the user or password it got is empty. How to find the missing variable.

Author: Formgong
Published: 2026-10-16
Language: en
Canonical: https://formgong.com/en/blog/nodemailer-missing-credentials-for-plain/

Missing credentials for "PLAIN" means Nodemailer was told to log in but got an empty user or password, usually because process.env.SMTP_USER or SMTP_PASS is undefined. The variables are in a .env file that is not loaded, have different names, or exist on your computer but not on the host. Set them where the code runs and restart or redeploy.

## Nodemailer, not the server, throws it

*Figure: A contact form shows an error after Send. The terminal shows that SMTP_USER and SMTP_PASS are undefined and Nodemailer throws Missing credentials for PLAIN.*

On 16.10.2026 we gave Node.js 22.23 and nodemailer 10.1.1 the auth object `{ user: process.env.SMTP_USER, pass: process.env.SMTP_PASS }` with both variables unset, and sent through `smtp.gmail.com`. It connected, saw that Gmail offers the `PLAIN` login method, and threw `Error: Missing credentials for "PLAIN"` with `code: "EAUTH"` and `command: "API"`.

`command: "API"` is the tell: the problem is in what your code passed, not in anything the server said. The word in quotes is the login method the server offered. A server that offers only `LOGIN`, such as Office 365, gives the same error with `"LOGIN"`.

## Where the value gets lost

- **The .env file is never loaded.** Plain Node does not read `.env` by itself. Start with `node --env-file=.env server.js` or load `dotenv` before the transport is created.

- **It is loaded too late.** If the module that creates the transport is imported before `dotenv.config()` runs, it read the variables while they were still empty.

- **The names differ.** `SMTP_PASSWORD` in the file, `SMTP_PASS` in the code.

- **It works locally but not on the host.** `.env` is not in Git, so Vercel, Netlify or your server never got it. Add the variables in the host's settings and redeploy. Our [Next.js guide](/en/blog/nextjs-contact-form-not-working/) shows this case on a Vercel-style build.

- **The code runs in the browser.** Variables without a public prefix are empty in front-end code, and an SMTP password must never be there anyway.

## Catch it at startup

Check the variables when the server starts, then call `verify()`. A missing value then stops the deploy with its name in the log, instead of failing quietly on the first real message.

```javascript
// Fail at startup with a clear message instead of failing on the first visitor's message.
for (const name of ["SMTP_HOST", "SMTP_PORT", "SMTP_USER", "SMTP_PASS"]) {
  if (!process.env[name]) throw new Error(`${name} is not set`);
}

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: Number(process.env.SMTP_PORT),
  secure: Number(process.env.SMTP_PORT) === 465, // true only for 465
  auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
});

await transporter.verify(); // connects and logs in, sends nothing
```

## Do you need SMTP credentials for a contact form?

If the SMTP login exists only so a contact form can email you, you can drop it: a form that posts to [Formgong](/en/) at `https://formgong.com/submit` has no SMTP host, port or password in your code, and Formgong sends the notification itself. See the [HTML guide](/en/docs/html/).

## Frequently asked questions

### What does Missing credentials for PLAIN mean in Nodemailer?

Nodemailer got an empty user or password in the auth option, so it could not log in with the PLAIN method the server offered. The SMTP server never saw a password.

### Why does it work locally but not on Vercel?

Your .env file stays on your computer. Add SMTP_USER and SMTP_PASS in the host's environment variables and redeploy.

### What is the difference between PLAIN and LOGIN in this error?

Nothing that matters for the fix. It is the login method the server offered; Gmail offers PLAIN, Office 365 offers LOGIN. Both errors mean the credentials are empty.

## Sources and documentation

- [Nodemailer: SMTP transport (auth options)](https://nodemailer.com/smtp/)
- [Node.js: --env-file](https://nodejs.org/api/cli.html#--env-fileconfig)
- [Vercel: Environment variables](https://vercel.com/docs/environment-variables)
- [HTML contact form](https://formgong.com/en/docs/html/)

[Get a form key](https://formgong.com/en/#top)
