Send email with Node.js
Send transactional email from Node.js three ways: a plain fetch call, the envello SDK, or Nodemailer over SMTP. With retries, idempotency and errors.
Updated · 2 min read
Sending a transactional email from Node.js is one authenticated POST to https://api.envello.dev/emails. Node 18 and later ship fetch, so you don't need any dependency for that. The envello SDK adds types and a cleaner call. Nodemailer over SMTP is there for apps that already send through it.
Before you start
You need an Envello account, a verified sending domain, and an API key. Keep the key in an environment variable and only read it on the server; it must never reach browser code.
- ENVELLO_API_KEY=env_live_... (create it in the dashboard under API keys)
- A from address on your verified domain, e.g. Acme <[email protected]>
- Node 18 or later for the built-in fetch
Option 1: plain fetch
No package to install or keep updated. A good fit for serverless functions and small services.
- const response = await fetch('https://api.envello.dev/emails', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ENVELLO_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ from: 'Acme <[email protected]>', to: '[email protected]', subject: 'Welcome!', html: '<p>Welcome aboard</p>' }) });
- if (!response.ok) throw new Error(`Envello send failed: ${response.status} ${await response.text()}`);
- const { id, status } = await response.json(); // status is 'queued' on success
Option 2: the envello SDK
npm install envello. The client takes your API key and exposes emails.send() with typed request and response objects.
- import { Envello } from 'envello';
- const envello = new Envello({ apiKey: process.env.ENVELLO_API_KEY });
- const { id } = await envello.emails.send({ from: 'Acme <[email protected]>', to: '[email protected]', subject: 'Welcome!', html: '<p>Welcome aboard</p>' });
Option 3: Nodemailer over SMTP
If your app already builds messages with Nodemailer, switch the transport and keep everything else. The SMTP username can be any non-empty value; the password is your API key.
- const transport = nodemailer.createTransport({ host: 'smtp.envello.dev', port: 587, auth: { user: 'envello', pass: process.env.ENVELLO_API_KEY } });
- await transport.sendMail({ from: 'Acme <[email protected]>', to: '[email protected]', subject: 'Welcome!', html: '<p>Welcome aboard</p>' });
- Port 465 works too, with secure: true for TLS from the first byte.
Retries without duplicate emails
Network calls fail, and a naive retry can send the same receipt twice. Send an Idempotency-Key header with a value that's stable for that one email, such as `receipt-${orderId}`. If the first request went through, a retry with the same key doesn't send a second email.
Retry on network errors and 5xx responses with backoff. Don't retry 4xx responses: they mean the request itself is wrong (an unverified from domain, a missing field, a bad key) and will fail the same way again.
Sending many emails at once
POST /emails/batch takes up to 100 emails in one request and reports failures per item, so one bad address doesn't fail the whole batch. Use it for things like a digest to many users, not for one-off sends.
HTML templates
Any template engine works, because the API takes rendered HTML. If you write emails as React components, render them with @react-email/render and pass the result as html. The React Email guide in the docs covers it.
Common errors
Delivery and bounce events show up in the dashboard logs with the raw event payload, including the receiving server's SMTP response. That's where to look once a message has left Envello but didn't arrive.
- 401: the API key is missing, revoked or sent without the Bearer prefix
- 403 domain_not_verified: the from domain isn't verified on your account yet
- 400: neither html nor text was provided, or a recipient address is malformed
Check your domain's SPF, DKIM, and DMARC records
Paste in a domain and see what's missing, plus the exact DNS records to fix it. Free, no account needed.
Check your domain →