TypeScript SDK
@atrix-mail/sdk is a small typed client over the sending API — one class, fully-typed requests and responses, a typed error, and webhook verification built in.
The SDK is ESM, strict-TypeScript, and dependency-light — it uses the runtime's fetch and node:crypto, and re-uses the platform's own request/response types so it can never drift from the API.
Install
bun add @atrix-mail/sdk # or: npm i @atrix-mail/sdk
Create a client
import { AtrixMail } from "@atrix-mail/sdk"; const mail = new AtrixMail(process.env.ATRIX_MAIL_KEY!); // am_live_... // options (all optional): new AtrixMail(apiKey, { baseUrl: "https://api.mail.atrix.dev", // default timeoutMs: 30_000, // default fetch: customFetch, // defaults to globalThis.fetch });
Send email
const { id, status } = await mail.emails.send({ from: "Yourco <hello@yourco.com>", to: ["ava@client.io"], subject: "Your March invoice", html: "<p>Invoice attached.</p>", text: "Invoice attached.", reply_to: "support@yourco.com", attachments: [ { filename: "invoice.pdf", content: pdfBase64, contentType: "application/pdf" }, ], idempotency_key: "invoice-1042", // safe retries }); // id: "<uuid>", status: "queued"
send takes the full SendEmailInput contract and returns { id, status }.
Retrieve a sent email
const email = await mail.emails.get(id); // needs an emails.read-scoped key console.log(email.status, email.events);
Beyond sending
The client covers the whole key-authenticated surface, not just transactional mail — each resource needs its own scope on the key.
// Domains — add one, read back what DNS is still wrong, re-check. const { dns_records } = await mail.domains.create("acme.com"); const { grade } = await mail.domains.get(domainId); await mail.domains.verify(domainId); // Audiences — lists and their contacts. const { audience } = await mail.audiences.create("Changelog"); await mail.audiences.importContacts(audience.id, [ { email: "ava@client.io", first_name: "Ava" }, ]); // Broadcasts — draft, then send or schedule. const { broadcast } = await mail.broadcasts.create({ audience_id: audience.id, name: "March changelog", from: "Yourco <hello@acme.com>", subject: "What shipped in March", html: "<p>…</p>", }); await mail.broadcasts.send(broadcast.id); // Mailboxes — hosting. The password is set once, here. const { mailbox } = await mail.mailboxes.create({ domain_id: domainId, local_part: "support", password: generatedPassword, }); const { connection } = await mail.mailboxes.connectionInfo(mailbox.id); // Webhooks — the secret comes back once, on create. const { webhook } = await mail.webhookEndpoints.create("https://acme.com/hook", [ "email.delivered", "email.bounced", ]); // Templates, and analytics. await mail.templates.create({ name: "welcome", subject: "Welcome, {{name}}", html: "<p>Hi {{name}}</p>" }); const stats = await mail.analytics.deliverability(); // 30-day rates + series const { entries } = await mail.analytics.activity({ status: "bounced" });
Public API
| Member | Signature |
|---|---|
| new AtrixMail | (apiKey: string, options?: AtrixMailOptions) |
| emails.send | (input: SendEmailInput) => Promise<SendEmailResponse> |
| emails.get | (id: string) => Promise<RetrievedEmail> |
| domains | create · list · get · update · verify · delete |
| mailboxes | create · list · get · update · delete · connectionInfo · createAlias · listAliases · deleteAlias |
| audiences | create · list · get · delete · addContact · importContacts · listContacts · deleteContact |
| broadcasts | create · list · get · send · delete |
| templates | create · list · get · update · delete |
| analytics | deliverability · activity |
| webhookEndpoints | create · list · setStatus · delete · deliveries · test |
| webhooks.constructEvent | (body, header, secret, opts?) => WebhookEvent |
| webhooks.verify | (body, header, secret, opts?) => boolean |
Errors
Every non-2xx response throws an AtrixMailError carrying the API's stable code, the HTTP status, and — for validation errors — a fields map. Network failures throw with status: 0 and code: "network_error".
import { AtrixMailError } from "@atrix-mail/sdk"; try { await mail.emails.send(payload); } catch (err) { if (err instanceof AtrixMailError) { if (err.is("domain_not_verified")) { /* fix the from domain */ } if (err.isRetryable) { // back off; err.retryAfter has the seconds from Retry-After on a 429 } console.error(err.code, err.status, err.fields); } }
Verify webhooks
webhooks.constructEvent verifies the atrix-signature header (HMAC + timestamp tolerance) and returns a typed event; narrow on event.type to get typed data. See Webhooks for the full example.
const event = mail.webhooks.constructEvent(rawBody, signatureHeader, webhookSecret); if (event.type === "email.delivered") { console.log(event.data.email_id); }