Node.js (@frontmail/node)
@frontmail/node is for servers, serverless functions and scripts. It authenticates with a
private key, so origin and Turnstile checks don’t apply, and it unlocks batch sending,
history and template schemas.
npm install @frontmail/nodeRequires Node.js 20+ (uses the built-in fetch). Also works in Bun, Deno and edge runtimes that
provide fetch.
Create a client
Section titled “Create a client”import { Frontmail } from '@frontmail/node';
const frontmail = new Frontmail({ privateKey: process.env.FRONTMAIL_PRIVATE_KEY!, // sk_… // retry: { retries: 3 }, timeoutMs: 15_000, apiUrl: 'https://api.frontmail.dev'});Without privateKey, the client reads FRONTMAIL_PRIVATE_KEY (and FRONTMAIL_API_URL) from the
environment. Never expose the private key to a browser bundle – keep it in environment variables or a secret
manager. See Public and private keys.
The package refuses to run in a web browser: importing it there logs a warning and new Frontmail()
throws private_key_in_browser (opt out with dangerouslyAllowPrivateKeyInBrowser: true only for
internal tools on trusted machines). In the browser use @frontmail/browser with
the public key.
const result = await frontmail.send({ serviceId: 'svc_01J9…', // optional – omit to use the default service templateId: 'tpl_order', params: { order_id: 'A-1042', items: [{ title: 'Room', qty: 2, price: 89 }], total: 178, currency: 'EUR', },});console.log(result.messageId, result.status); // 'queued' | 'held'Other input fields: idempotencyKey (recommended when the send is triggered by something that can repeat,
e.g. a webhook – use its event ID), attachments, signal.
Batch sends
Section titled “Batch sends”Send up to 100 messages in one request. Each message is validated and billed on its own –
one invalid message doesn’t fail the others. sendBatch doesn’t throw for per-message errors;
check ok on each result.
const results = await frontmail.sendBatch([ { serviceId: 'svc_01J9…', templateId: 'tpl_reminder', params: { name: 'Jana' }, idempotencyKey: 'reminder-42-jana' }, { serviceId: 'svc_01J9…', templateId: 'tpl_reminder', params: { name: 'Petr' }, idempotencyKey: 'reminder-42-petr' },]);
for (const r of results) { if (!r.ok) console.error(r.index, r.error.code); else console.log(r.index, r.messageId, r.status);}A batch counts as one request for rate limits. For more than 100 messages, split them into chunks.
History
Section titled “History”const page = await frontmail.history({ status: 'failed', templateId: 'tpl_order', from: '2026-09-01T00:00:00Z', limit: 50,});
for (const m of page.items) console.log(m.messageId, m.status, m.to, m.subject);
// Next pageif (page.nextCursor) await frontmail.history({ cursor: page.nextCursor });
// Or iterate over everythingfor await (const m of frontmail.history({ status: 'bounced' })) console.log(m.to);Filters: status, templateId, serviceId, from, to, q (recipient search), limit
(≤ 100), cursor. Awaiting history() returns one page; for await walks through all pages lazily.
History covers your plan’s retention period.
Message status
Section titled “Message status”const msg = await frontmail.getMessage('msg_01J9Z3K7Q2');// { messageId, status, createdAt, sentAt, templateId, serviceId, events: [{ type, at }, …] }Templates
Section titled “Templates”const templates = await frontmail.templates.list(); // param schemas, used by `frontmail types`const contact = await frontmail.templates.get('tpl_contact');The package includes the frontmail CLI – see Typed params.
Example project: examples/node.