# Send email with Express

> Send email from an Express route with Email Fast: @email-fast/nodejs in a POST handler with async error handling and an idempotency key, on a sandbox key.

Canonical: https://emailfast.dev/send-email-with/express

In Express, email lives in a route handler: parse the body, call `ef.send`,
return the id. Create one `EmailFast` client at module scope and share it across
requests — it is stateless and cheap. The only Express-specific work is error
propagation, because Express 4 does nothing with a rejected async handler.

```bash
npm install @email-fast/nodejs
```

```js
// server.mjs
import express from "express";
import { EmailFast } from "@email-fast/nodejs";

const app = express();
app.use(express.json());

const ef = new EmailFast({ apiKey: process.env.EMAILFAST_API_KEY, baseUrl: "https://api.emailfast.dev" }); // ef_sandbox_… while testing

app.post("/signup", async (req, res, next) => {
  const { email, name } = req.body;
  try {
    const { id } = await ef.send({
      to: email,
      subject: "Confirm your account",
      html: "<p>Hi {{name}}, confirm your address here.</p>",
      data: { name },
      idempotency_key: `confirm-${email}`, // a double-POST can't double-send
    });
    res.status(202).json({ id });
  } catch (err) {
    next(err); // Express 4 won't catch async throws for you
  }
});

app.use((err, req, res, next) => {
  console.error(err);
  res.status(502).json({ error: "email send failed" });
});

app.listen(3000);
```

a 202 from the API means the send is committed to a durable, partitioned outbox before we answer — a crash can't lose it, and a retry with the same idempotency key can't double-send.

## Gotchas

- Express 4 silently drops rejected promises from async handlers — wrap in `try/catch` and call `next(err)`. Express 5 propagates them, but the explicit catch works in both.
- Without `app.use(express.json())`, `req.body` is `undefined` and every send fails on a missing recipient.
- Don't loop sends inside a request: `/v1/emails/batch` takes up to 500 messages in one call, with per-recipient idempotency.
- Key the idempotency on domain data (`confirm-${email}`), so client retries and refresh-resubmits collapse into one send.

## Next

- [Full Node.js quickstart](/docs/quickstart-node) — timelines, webhooks, batch
- [The email API](/features/email-api) — batch, scheduling, suppressions
- [Sending from Fastify](/send-email-with/fastify)
- [All docs](/docs)
