# Quickstart: Node.js

> Send your first email from Node.js in under five minutes: install @email-fast/nodejs, create a sandbox key, send, and verify the event timeline.

Canonical: https://emailfast.dev/docs/quickstart-node

## 1. Install

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

## 2. Send

```js
import { EmailFast } from "@email-fast/nodejs";

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

const { id, status } = await ef.send({
  to: "ada@example.com",
  subject: "Welcome to the ledger",
  html: "<h1>Hi {{name}}</h1><p>Your account is ready.</p>",
  data: { name: "Ada" },
  idempotency_key: "welcome-ada-1", // retry-safe: same key can never double-send
});

console.log(id, status); // "msg_…", "queued"
```

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.

## 3. Watch it happen

```js
const timeline = await ef.getMessage(id);
console.log(timeline.events); // admitted → rendered → (sandbox-)delivered …
```

With a sandbox key the message lands in your hosted capture inbox — same pipeline,
nothing sent: sandbox keys (ef_sandbox_…) that run the real pipeline dry: real validation, real rendering, real events, a hosted capture inbox — and no email leaves.

## 4. Hear about it via webhook

```js
// Your Express handler (raw body needed for signature verification):
import crypto from "node:crypto";

app.post("/hooks/email", express.raw({ type: "application/json" }), (req, res) => {
  // x-emailfast-signature: "t=<unix>,v1=<hex>" — HMAC-SHA256 over `<t>.<rawBody>`
  const sig = Object.fromEntries(
    req.get("x-emailfast-signature").split(",").map((p) => p.split("=")),
  );
  const expected = crypto
    .createHmac("sha256", process.env.EMAILFAST_WEBHOOK_SECRET)
    .update(`${sig.t}.${req.body}`)
    .digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(sig.v1), Buffer.from(expected))) {
    return res.sendStatus(400);
  }
  const event = JSON.parse(req.body);
  // event.type: email.sent | email.bounced | email.opened | …
  res.sendStatus(200);
});
```

webhooks signed with timestamped HMAC-SHA256 (Stripe-style t=…,v1=…), with delivery hardened against server-side request forgery. Reject anything older than a few minutes (compare `sig.t`
against the clock) to close the replay window.

## Next

- [Batch & scheduled sends](/features/email-api)
- [Templates & MJML](/features/templates)
- [Migrating from SendGrid/Mailgun/Postmark](/features/migration)
