Reference / Mail

Mail

Manage verified domains, IMAP mailboxes, MJML templates, and send transactional or bulk email — all through the same client.

Domains#

Verified sending domains for your company. Domain creation and DNS verification happen in the dashboard; the API surfaces a read-only view for use at send time.

List domains#

GET/api/mail/companies/{slug}/domains
const domains = await sentroy.domains.list()
// → Domain[]

Get domain#

GET/api/mail/companies/{slug}/domains/{id}
const domain = await sentroy.domains.get("domain-id")

Mailboxes#

IMAP mailbox accounts created against your verified domains. Used by the Inbox API to read messages and by Send to authenticate the from-address.

List mailboxes#

GET/api/mail/companies/{slug}/mailboxes
const mailboxes = await sentroy.mailboxes.list()

Templates#

Reusable MJML email templates with multilingual fields and variable placeholders.

List templates#

GET/api/mail/companies/{slug}/templates
const templates = await sentroy.templates.list()

Get template#

GET/api/mail/companies/{slug}/templates/{id}
const template = await sentroy.templates.get("template-id")

LocalizedString shape

Templates support multiple languages. A field can be a plain string (single-language) or an object keyed by language code:

{
  "id": "b3f1a2c4-...",
  "name": { "en": "Welcome Email", "tr": "Hosgeldin E-postasi" },
  "subject": { "en": "Welcome, {{name}}!", "tr": "Hosgeldin, {{name}}!" },
  "mjmlBody": { "en": "<mjml>...</mjml>", "tr": "<mjml>...</mjml>" },
  "variables": ["name", "company"],
  "domainId": "a1b2c3d4-...",
  "domainName": "example.com",
  "createdAt": "2026-01-15T10:30:00.000Z",
  "updatedAt": "2026-04-10T14:22:00.000Z"
}

Use the variables array to know which placeholders the template expects.

Inbox#

Read messages, list IMAP folders, group threads, and manage message state.

List messages#

GET/api/mail/companies/{slug}/inbox?mailbox=info@example.com&folder=INBOX&page=1&limit=20
const messages = await sentroy.inbox.list({
  mailbox: "info@example.com",
  folder: "INBOX",
  page: 1,
  limit: 20,
})

Get message#

GET/api/mail/companies/{slug}/inbox/{uid}?mailbox=info@example.com
const message = await sentroy.inbox.get(1234, {
  mailbox: "info@example.com",
})

List folders#

GET/api/mail/companies/{slug}/inbox/folders?mailbox=info@example.com
const folders = await sentroy.inbox.listFolders("info@example.com")

Get thread#

GET/api/mail/companies/{slug}/inbox/thread?subject=Re%3A%20Project%20update&mailbox=info@example.com
const thread = await sentroy.inbox.getThread(
  "Re: Project update",
  "info@example.com",
)

Mark as read / unread#

PATCH/api/mail/companies/{slug}/inbox/{uid}/read
await sentroy.inbox.markAsRead(1234, { mailbox: "info@example.com" })
await sentroy.inbox.markAsUnread(1234, { mailbox: "info@example.com" })

Move message#

POST/api/mail/companies/{slug}/inbox/{uid}/move
await sentroy.inbox.move(1234, "Trash", {
  from: "INBOX",
  mailbox: "info@example.com",
})

Delete message#

DELETE/api/mail/companies/{slug}/inbox/{uid}?mailbox=info@example.com
await sentroy.inbox.delete(1234, { mailbox: "info@example.com" })

Send email#

Single endpoint for transactional and bulk send. Pass either a templateId + variables, or raw html. Recipients can be a string or array of addresses.

Send with a template#

POST/api/mail/companies/{slug}/send
const result = await sentroy.send.email({
  to: "user@example.com",
  from: "info@example.com",
  subject: "Welcome!",
  domainId: "domain-id",
  templateId: "template-id",
  variables: {
    name: "John",
    company: "Acme",
  },
})

Send in a specific language#

For multilingual templates, pass langto pick which translation of subject and body to use. If omitted, the template's default language wins.

POST/api/mail/companies/{slug}/send
await sentroy.send.email({
  to: "user@example.com",
  from: "info@example.com",
  subject: "Hosgeldin!",
  domainId: "domain-id",
  templateId: "template-id",
  lang: "tr",
  variables: { name: "Ahmet" },
})

Send with raw HTML#

POST/api/mail/companies/{slug}/send
await sentroy.send.email({
  to: ["user1@example.com", "user2@example.com"],
  from: "info@example.com",
  subject: "Hello",
  domainId: "domain-id",
  html: "<h1>Hello World</h1>",
})

Send with attachments#

Attachments accept a base64 content string plus filename and MIME type.

POST/api/mail/companies/{slug}/send
await sentroy.send.email({
  to: "user@example.com",
  from: "info@example.com",
  subject: "Invoice",
  domainId: "domain-id",
  html: "<p>Please find your invoice attached.</p>",
  attachments: [
    {
      filename: "invoice.pdf",
      content: base64String,
      contentType: "application/pdf",
    },
  ],
})

Audience#

Manage contacts and audience lists. Build your own newsletter signup, sync customers from another system, or assemble segments for a campaign.

List contacts#

Paginated browsing with optional status and tag filters. Tags are comma-joined on the wire — pass them as an array to the SDK.

GET/api/mail/companies/{slug}/audience/contacts?page=1&limit=50&status=active&tags=customer,vip
const { contacts, total, page, limit } = await sentroy.audience.contacts.list({
  page: 1,
  limit: 50,
  status: "active",
  tags: ["customer", "vip"],
})

Create contact#

POST/api/mail/companies/{slug}/audience/contacts
const contact = await sentroy.audience.contacts.create({
  email: "user@example.com",
  name: "Jane Doe",
  tags: ["beta-tester"],
  metadata: { signupSource: "landing-2026-q2" },
})

Update contact#

Pass any subset of fields. Use status to mark unsubscribed/bounced.

PATCH/api/mail/companies/{slug}/audience/contacts/{id}
await sentroy.audience.contacts.update(contact.id, { tags: ["customer"] })

Delete contact#

Soft-delete — sets status: "unsubscribed". The record is preserved so historical mail-log foreign keys keep resolving and the email can't accidentally be re-added.

DELETE/api/mail/companies/{slug}/audience/contacts/{id}
await sentroy.audience.contacts.delete(contact.id)

Audience lists#

Lists are simple groupings; a single contact can belong to many. Use them as the target of a campaign or a form submission.

GET/api/mail/companies/{slug}/audience/lists
const lists = await sentroy.audience.lists.list()
POST/api/mail/companies/{slug}/audience/lists
const list = await sentroy.audience.lists.create({
  name: "Newsletter — May 2026",
  description: "Opt-ins from the homepage form",
})
DELETE/api/mail/companies/{slug}/audience/lists/{id}
await sentroy.audience.lists.delete(list.id)

List membership#

Membership operations are scoped via the SDK's members(listId) accessor — keeps the list id off every call.

POST/api/mail/companies/{slug}/audience/lists/{id}/members
const members = sentroy.audience.lists.members(list.id)
await members.add(contact.id)
GET/api/mail/companies/{slug}/audience/lists/{id}/members
const inList = await members.list()
DELETE/api/mail/companies/{slug}/audience/lists/{id}/members
await members.remove(contact.id)

Suppressions#

Suppressed addresses are skipped at send time. Bounces and complaints are added automatically by the mail server — the API is for honoring off-platform opt-outs or removing a stale entry.

List suppressions#

GET/api/mail/companies/{slug}/suppressions?page=1&limit=50&domainId=domain-id&reason=complaint
const suppressions = await sentroy.suppressions.list({
  domainId: "domain-id",
  reason: "complaint",
  page: 1,
  limit: 50,
})

Add suppression#

POST/api/mail/companies/{slug}/suppressions
const added = await sentroy.suppressions.add({
  email: "leaving@example.com",
  domainId: "domain-id",
  reason: "manual",
})

Remove suppression#

Removing a suppression makes the address eligible to receive mail again.

DELETE/api/mail/companies/{slug}/suppressions/{id}
await sentroy.suppressions.remove(added.id)

Webhooks#

Subscribe to delivery events on a per-domain basis. Each delivery is signed with the secret returned at create time — verify the HMAC on your endpoint before trusting the payload.

Create webhook#

POST/api/mail/companies/{slug}/webhooks
const webhook = await sentroy.webhooks.create({
  url: "https://example.com/webhooks/sentroy",
  events: ["sent", "bounced", "opened", "clicked", "unsubscribed"],
  domainId: "domain-id",
})

console.log(webhook.secret) // Returned ONCE — store it now

Event types#

NameTypeDescription
sentstringMail handed off to SMTP successfully
bouncedstringHard or soft bounce reported by remote MTA
failedstringSend pipeline failure (rendering, suppression, quota)
openedstringTracking pixel hit (requires trackOpens at send time)
clickedstringLink click recorded (requires trackClicks at send time)
unsubscribedstringRecipient clicked the {{unsubscribe_url}} link

List + scope#

GET/api/mail/companies/{slug}/webhooks
const all = await sentroy.webhooks.list()
const scoped = await sentroy.webhooks.list("domain-id")

Test fire#

Manual dispatch — POST a custom payload at the webhook's current URL. The result and a row in the delivery log are returned for inspection. The mail server's automated event delivery is unaffected; this is a debug tool, not a production retry path.

POST/api/mail/companies/{slug}/webhooks/{id}/test
const result = await sentroy.webhooks.test(webhook.id, {
  event: "sent",
  payload: {
    mailLogId: "ml_abc",
    to: "user@example.com",
    subject: "Welcome",
  },
})

console.log(result.responseStatus, result.durationMs)

List deliveries#

Paginated history of test/replay dispatches recorded for the webhook. Each row carries the full payload, response body, status, and round-trip duration.

GET/api/mail/companies/{slug}/webhooks/{id}/deliveries?page=1&limit=50&status=failed
const { items, total } = await sentroy.webhooks
  .deliveries(webhook.id)
  .list({ page: 1, limit: 50, status: "failed" })

Get delivery#

GET/api/mail/companies/{slug}/webhooks/{id}/deliveries/{deliveryId}
const delivery = await sentroy.webhooks
  .deliveries(webhook.id)
  .get(deliveryId)

Replay delivery#

Re-fire the recorded payload at the webhook's current URL. Useful for retesting after the receiver fixes a bug. The new row is linked to the original via replayOf.

POST/api/mail/companies/{slug}/webhooks/{id}/deliveries/{deliveryId}/replay
const result = await sentroy.webhooks
  .deliveries(webhook.id)
  .replay(deliveryId)

Update + delete#

PATCH/api/mail/companies/{slug}/webhooks/{id}
await sentroy.webhooks.update(webhook.id, { active: false })
DELETE/api/mail/companies/{slug}/webhooks/{id}
await sentroy.webhooks.delete(webhook.id)

Logs#

Query the mail log to debug delivery issues, surface per-message status in your own UI, or build a customer-facing activity timeline.

List logs#

GET/api/mail/companies/{slug}/logs?status=bounced&domainId=domain-id&from=2026-05-01T00:00:00Z&to=2026-05-31T23:59:59Z&page=1&limit=100
const logs = await sentroy.logs.list({
  status: "bounced",
  domainId: "domain-id",
  from: "2026-05-01T00:00:00Z",
  to: "2026-05-31T23:59:59Z",
  page: 1,
  limit: 100,
})

Get a single entry#

GET/api/mail/companies/{slug}/logs/{id}
const log = await sentroy.logs.get(logs[0].id)
console.log(log.openedAt, log.clickedAt) // tracking timestamps if enabled