API
Node client
A small typed client for Node 18+ with safe retries, error details and webhook verification.
The Node client is one file with no dependencies and full TypeScript types. It is not on npm yet, so download one file into your project: emit.ts for TypeScript, or emit.mjs with emit.d.mts for JavaScript.
import { Emit, EmitError } from "./emit";
const emit = new Emit({ apiKey: process.env.EMIT_API_KEY! });
try {
const { id } = await emit.send({
from: "Your Store <orders@yourstore.com>",
to: customer.email,
subject: `Order ${order.number} confirmed`,
html,
idempotencyKey: `order-${order.id}-confirmation`,
});
} catch (err) {
if (err instanceof EmitError) console.error(err.code, err.docs);
}Try it in two minutes#
You need a verified domain and an API key (see the Quickstart). Download the client next to a test file, and keep the key in an environment variable. Never paste it into the file.
curl -O https://app.emitemail.com/sdk/emit.ts
export EMIT_API_KEY="emt_live_..."Save this as test-emit.ts, changing from to an address on your verified domain and to to your own address:
import { Emit } from "./emit";
const emit = new Emit({ apiKey: process.env.EMIT_API_KEY! });
const { id } = await emit.send({
from: "Your Store <orders@yourstore.com>",
to: "you@example.com",
subject: "Hello from emitemail",
text: "Your typed client works.",
});
console.log("sent", id);Run it with npx tsx test-emit.ts. Then delete the text line: your editor should flag the email as missing a body before you run anything.
What it does for you#
- Catches mistakes early. Your editor flags an email with no
htmlortext, a missingtoorsubject, or a misspelled field. At runtime it refuses an email with no recipients, a subject with a line break, or more than 50 recipients without calling emitemail. - Sends once. Retries use the same idempotency key, so a retry after a timeout never emails the customer twice.
- Retries what is safe. Network failures and 5xx answers are retried. Refusals, including the monthly cap, never are.
- Waits out bursts. A
rate_limitedanswer is waited out automatically when emitemail says it is up to 15 seconds. - Explains errors.
EmitErrorhascode,status,retryable,maybeSent,retryAfterand adocslink.
Verify webhooks#
import { parseWebhook } from "./emit";
// Use the raw request body, not parsed JSON.
const event = parseWebhook(process.env.EMIT_WEBHOOK_SECRET!, rawBody, req.header("emit-signature")!);
if (event.type === "email.bounced") markAddressBad(event.data.to);parseWebhook throws an EmitError with the code invalid_signature if the signature is wrong or the request is more than 5 minutes old. Webhooks explains the format.