Guides
Send email with Fastify
Schema-validated body in, message id out — the Fastify way.
Facts verified 2026-07-17 — corrections: hello@emailfast.dev
Fastify's schema validation is the right front door for email: declare the body shape once and malformed requests never reach your send call. Handlers are async by default — return the JSON, throw on failure — so the send is three honest lines with @email-fast/nodejs.
npm install fastify @email-fast/nodejs// server.mjs
import Fastify from "fastify";
import { EmailFast } from "@email-fast/nodejs";
const app = Fastify({ logger: true });
const ef = new EmailFast({ apiKey: process.env.EMAILFAST_API_KEY, baseUrl: "https://api.emailfast.dev" }); // ef_sandbox_… while testing
app.post(
"/invite",
{
schema: {
body: {
type: "object",
required: ["email", "team"],
properties: {
email: { type: "string" },
team: { type: "string" },
},
},
},
},
async (req, reply) => {
const { email, team } = req.body;
const { id } = await ef.send({
to: email,
subject: "You're invited",
html: "<p>Join {{team}} here.</p>",
data: { team },
idempotency_key: `invite-${team}-${email}`, // a retried request can't double-invite
});
reply.code(202);
return { id };
},
);
app.setErrorHandler((err, req, reply) => {
req.log.error(err);
reply.code(err.validation ? 400 : 502).send({ error: err.message });
});
await app.listen({ port: 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
format: "email"in a JSON schema is silently ignored unless you registerajv-formats— Fastify's default Ajv build skips string formats. Validate presence and type locally; the API rejects invalid addresses with a clear error body.- Whatever an async handler returns is the reply; a thrown error becomes a 500 unless
setErrorHandlermaps it. Route send failures to 502, validation failures to 400. - In larger apps, register the client once in a plugin and
app.decorate("emails", ef)so every route shares one instance. - Build idempotency keys from route data (
invite-${team}-${email}) so retries collapse instead of duplicating.
Next
- Full Node.js quickstart — timelines, webhooks, batch
- The email API — batch, scheduling, suppressions
- Sending from Express
- All docs