Transports

SNDR

Send transactional email through the SNDR HTTP API with createMailer.

Use SNDR for app-triggered email — receipts, sign-in mail, and other transactional sends — over HTTPS JSON. Wire SndrTransport into createMailer; do not call @rkiza/sndr from application code.

The one rule

Import from sently/transports/sndr, pass the transport to await createMailer(...), and send with the mailer — keep delivery on the email channel.

Quick start

Configure

import { createMailer } from "sently/mailer";
import { SndrTransport } from "sently/transports/sndr";

const mailer = await createMailer({
  transport: new SndrTransport({
    apiKey: process.env.SNDR_API_KEY!,
  }),
});

Send

const result = await mailer.send({
  from: "hello@yourdomain.com",
  to: "customer@example.com",
  subject: "Welcome aboard",
  html: "<p>Thanks for joining us.</p>",
});

See the result

console.log(result.messageId); // e.g. em_…
console.log(result.response); // e.g. queued

from must use a domain verified in the SNDR dashboard (SPF, DKIM, DMARC).

Configuration

OptionTypeDefaultMeaning
apiKeystringrequiredBearer key (sndr_live_… / sndr_test_…).
baseUrlstringhttps://api.sndr.shAPI origin (no trailing slash needed).
defaultTemplateIdstringDefault SNDR template_id when not set per message.

Message mapping

Mail optionSNDR fieldNotes
fromfromMIME form Name <addr> when a display name is present.
to / cc / bccto / cc / bccArrays of email addresses.
replyToreply_toFirst address only.
subjectsubjectRequired.
html / texthtml / textOptional body fields.
headersheadersCustom headers; template header is stripped.
idempotencyKey / messageIdIdempotency-KeySent when present.
datavariablesOnly when a template id is set.

POST /v1/send is idempotent when Idempotency-Key is supplied.

Templates

Set a template with the x-sndr-template-id header (or defaultTemplateId on the transport). Pass template variables through data ({{ variable }} placeholders on SNDR).

import { SNDR_TEMPLATE_ID_HEADER } from "sently/transports/sndr";

await mailer.send({
  from: "hello@yourdomain.com",
  to: "customer@example.com",
  subject: "Welcome",
  headers: { [SNDR_TEMPLATE_ID_HEADER]: "tpl_welcome" },
  data: { name: "Ada" },
});

Verify and errors

verify() calls GET /v1/domains with the same Bearer key.

Failed HTTP responses throw SndrError (SentlyError) with the API error.message when present.

SNDR error.code (common)Meaning
invalid_requestMalformed payload
unauthenticatedMissing or invalid API key
rate_limitedSlow down
domain_not_verifiedVerify the sending domain first
recipient_suppressedAddress is on the suppression list

Webhooks

Import from sently/webhooks/sndr. Verify X-Sndr-Signature (t=…,v1=…) over the raw body, then parse.

SNDR eventNormalized EmailEvent.type
email.queueddeferred
email.delivereddelivered
email.bouncedbounced
email.failedunknown
email.complainedcomplained
email.openedopened
email.clickedclicked
email.unsubscribedunknown

See Webhooks for the HMAC details.

What sently covers vs SNDR platform

SurfaceIn sentlyNotes
Send (POST /v1/send)Yes — SndrTransportChannel: createMailer
Templates + variablesYesHeader / defaultTemplateId + data
IdempotencyYesidempotencyKey / messageId
Domain verifyYes — verify()GET /v1/domains
Delivery webhooksYes — sently/webhooks/sndrSigned parse
Contacts / contact groups / broadcastsNoSNDR dashboard / their API
Analytics / suppressions adminNoUse SNDR dashboard
Attachments on HTTP sendNot mappedPrefer SMTP or ask SNDR if their send API gains attachments

Troubleshooting

Contact & resources

ContactDetail
Direct emailsndr@rkiza.sa
Contact formsndr.sh/contact
Docssndr.sh/docs
API referenceAPI Reference
Statussndr.sh/status
X / Twitter@usesndr
LinkedInSNDR company
GitHubgithub.com/rkiza/sndr

Learn more

  • Email channel — mailer options and send pipeline
  • Webhooks — SNDR delivery events and signature verification
  • Bundle size — why subpath imports stay small

Next

On this page