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. queuedfrom must use a domain verified in the SNDR dashboard (SPF, DKIM, DMARC).
Configuration
| Option | Type | Default | Meaning |
|---|---|---|---|
apiKey | string | required | Bearer key (sndr_live_… / sndr_test_…). |
baseUrl | string | https://api.sndr.sh | API origin (no trailing slash needed). |
defaultTemplateId | string | — | Default SNDR template_id when not set per message. |
Message mapping
| Mail option | SNDR field | Notes |
|---|---|---|
from | from | MIME form Name <addr> when a display name is present. |
to / cc / bcc | to / cc / bcc | Arrays of email addresses. |
replyTo | reply_to | First address only. |
subject | subject | Required. |
html / text | html / text | Optional body fields. |
headers | headers | Custom headers; template header is stripped. |
idempotencyKey / messageId | Idempotency-Key | Sent when present. |
data | variables | Only 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_request | Malformed payload |
unauthenticated | Missing or invalid API key |
rate_limited | Slow down |
domain_not_verified | Verify the sending domain first |
recipient_suppressed | Address is on the suppression list |
Webhooks
Import from sently/webhooks/sndr. Verify X-Sndr-Signature (t=…,v1=…) over the raw body, then parse.
| SNDR event | Normalized EmailEvent.type |
|---|---|
email.queued | deferred |
email.delivered | delivered |
email.bounced | bounced |
email.failed | unknown |
email.complained | complained |
email.opened | opened |
email.clicked | clicked |
email.unsubscribed | unknown |
See Webhooks for the HMAC details.
What sently covers vs SNDR platform
| Surface | In sently | Notes |
|---|---|---|
Send (POST /v1/send) | Yes — SndrTransport | Channel: createMailer |
| Templates + variables | Yes | Header / defaultTemplateId + data |
| Idempotency | Yes | idempotencyKey / messageId |
| Domain verify | Yes — verify() | GET /v1/domains |
| Delivery webhooks | Yes — sently/webhooks/sndr | Signed parse |
| Contacts / contact groups / broadcasts | No | SNDR dashboard / their API |
| Analytics / suppressions admin | No | Use SNDR dashboard |
| Attachments on HTTP send | Not mapped | Prefer SMTP or ask SNDR if their send API gains attachments |
Troubleshooting
SNDR rejects sends from unverified domains. Add SPF, DKIM, and DMARC in the dashboard Domains page, click Verify, then retry with that domain in from.
The address is on SNDR’s suppression list (bounce/complaint). Remove it only if the recipient opted back in; otherwise pick another address.
No. Use sently/transports/sndr. The main sently package does not re-export HTTP provider transports.
No. Use createMailer plus SndrTransport. Keep vendor extras off the shared email contract.
Contact & resources
| Contact | Detail |
|---|---|
| Direct email | sndr@rkiza.sa |
| Contact form | sndr.sh/contact |
| Docs | sndr.sh/docs |
| API reference | API Reference |
| Status | sndr.sh/status |
| X / Twitter | @usesndr |
| SNDR company | |
| GitHub | github.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