Sendry Docs
API Reference

Emails

Send transactional and marketing emails via the REST API.

Base URL

https://api.sendry.online/v1

All endpoints require Authorization: Bearer <api_key>.


Send email

POST /v1/emails

Requires sending_access scope.

Request body

FieldTypeRequiredDescription
fromstringYesSender address. Format: Name <email@domain.com> or email@domain.com. Domain must be verified.
tostring | string[]YesRecipient(s). Up to 50 addresses.
subjectstringYesEmail subject line.
htmlstringNo*HTML body. Max 5 MB. At least one of html or text is required (unless using template_id).
textstringNo*Plain-text body. Max 1 MB.
ccstring | string[]NoCC recipients.
bccstring | string[]NoBCC recipients.
reply_tostringNoReply-to address.
attachmentsAttachment[]NoBase64-encoded file attachments.
tagsTag[]NoCustom key/value tags for filtering in analytics.
headersRecord<string, string>NoCustom email headers (e.g., X-* headers).
scheduled_atstring (ISO 8601)NoSchedule delivery for a future time. Must be in the future, max 1 year ahead.
template_idstringNoUse a saved template. Overrides html/text.
variablesRecord<string, any>NoTemplate substitution variables.
trackingboolean | objectNoOpen/click tracking for this message. Default true.

tracking is per message and takes precedence over the sending domain's track_opens / track_clicks. Omit it to follow the domain. A boolean sets both kinds at once; an object sets one without touching the other:

{ "tracking": false }               // no pixel, links delivered exactly as written
{ "tracking": { "clicks": false } } // links left alone, opens still recorded
{ "tracking": { "opens": false } }  // no pixel, links still rewritten

Turn clicks off for any message whose links carry a one-time credential, such as a password reset or a signing link: a rewritten link sends that credential through the tracking domain first. Leaving opens alone means you still see whether the message was read.

GET /v1/emails/:id returns the tracking that actually applied, as tracking: { opens, clicks }, so a message with no open events can be told apart from one that was never tracked.

Attachment object:

{
  filename: string;       // e.g., "invoice.pdf"
  content: string;        // Base64-encoded file content
  content_type?: string;  // MIME type, default "application/octet-stream"
}

Tag object:

{
  name: string;   // e.g., "category"
  value: string;  // e.g., "welcome"
}

Example request

curl -X POST https://api.sendry.online/v1/emails \
  -H "Authorization: Bearer $SENDRY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <hello@acme.com>",
    "to": "user@example.com",
    "subject": "Welcome to Acme!",
    "html": "<h1>Welcome!</h1><p>Thanks for signing up.</p>",
    "tags": [{"name": "category", "value": "welcome"}]
  }'

Response

{
  "id": "email_abc123",
  "from": "Acme <hello@acme.com>",
  "to": ["user@example.com"],
  "subject": "Welcome to Acme!",
  "status": "queued",
  "created_at": "2025-03-12T09:00:00Z"
}

Send marketing email

POST /v1/emails/marketing

Requires sending_access scope. Marketing emails automatically include List-Unsubscribe and List-Unsubscribe-Post headers for CAN-SPAM compliance.

Request body

Same as Send email, plus:

FieldTypeRequiredDescription
unsubscribe_urlstringYesURL added to List-Unsubscribe header. Required for CAN-SPAM.
list_idstringNoMailing list identifier used for list-level unsubscribe tracking.

Send batch

POST /v1/emails/batch

Requires sending_access scope. Send up to 100 emails in a single request. All emails are queued atomically.

Request body

FieldTypeRequiredDescription
emailsEmailItem[]YesArray of 1–100 email objects.
fromstringNoDefault sender address for all emails (can be overridden per-email).
subjectstringNoDefault subject for all emails (can be overridden per-email).
template_idstringNoDefault template for all emails.

Each item in emails accepts all send fields except from, subject, and template_id at the top level can serve as defaults.

Example request

curl -X POST https://api.sendry.online/v1/emails/batch \
  -H "Authorization: Bearer $SENDRY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <hello@acme.com>",
    "subject": "Your order update",
    "emails": [
      {"to": "alice@example.com", "html": "<p>Alice, your order shipped!</p>"},
      {"to": "bob@example.com", "html": "<p>Bob, your order shipped!</p>"}
    ]
  }'

Response

{
  "data": [
    {"id": "email_abc123", "status": "queued"},
    {"id": "email_abc124", "status": "queued"}
  ]
}

Get email

GET /v1/emails/:id

Requires read_only scope.

Response

{
  "id": "email_abc123",
  "from": "Acme <hello@acme.com>",
  "to": ["user@example.com"],
  "subject": "Welcome to Acme!",
  "status": "delivered",
  "created_at": "2025-03-12T09:00:00Z",
  "sent_at": "2025-03-12T09:00:01Z",
  "last_event": "opened",
  "region": "us-east-1",
  "attachments": []
}

Status values: queued, sent, delivered, bounced, complained


Update a scheduled email

PATCH /v1/emails/:id

Requires full_access scope. Updates a queued email that has not been sent yet.

Request body

All fields are optional; include only the fields you want to change.

FieldTypeRequiredDescription
subjectstringNoNew subject line.
htmlstringNoNew HTML body. Max 5 MB.
textstringNoNew plain-text body. Max 1 MB.
scheduled_atstringNoNew ISO 8601 scheduled send time.

Example request

{
  "subject": "Updated: your order has shipped",
  "scheduled_at": "2025-03-16T09:00:00Z"
}

Response

Returns the updated email object, same shape as Get email.


List emails

GET /v1/emails

Requires read_only scope.

Query parameters

ParameterTypeDefaultDescription
limitnumber50Results per page (1–100).
cursorstringPagination cursor from previous response's next_cursor.
statusstringFilter by status: queued, sent, delivered, bounced, complained.

Response

{
  "data": [
    {
      "id": "email_abc123",
      "from": "Acme <hello@acme.com>",
      "to": ["user@example.com"],
      "subject": "Welcome to Acme!",
      "status": "delivered",
      "created_at": "2025-03-12T09:00:00Z",
      "sent_at": "2025-03-12T09:00:01Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Pagination is cursor-based. Pass next_cursor as the cursor parameter to get the next page.


List events for an email

GET /v1/emails/:id/events

Requires read_only scope. Lists every delivery event for one email (opens, clicks, bounces, complaints, and so on). Every event carries the individual recipient it belongs to; a message addressed to more than one recipient is delivered as one copy per recipient, each with its own tracking pixel and tracked links. Events recorded before per-recipient delivery shipped may carry the recipient *, meaning the message as a whole.

Query parameters

ParameterTypeDefaultDescription
limitnumber50Results per page (1–100).
cursorstringPagination cursor from the previous response's next_cursor.
typestringFilter by event type: sent, delivered, opened, clicked, bounced, complained, unsubscribed.

Response

{
  "data": [
    {
      "id": "evt_abc123",
      "email_id": "email_abc123",
      "type": "delivered",
      "recipient": "user@example.com",
      "metadata": null,
      "created_at": "2025-03-12T09:00:05Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Pagination is cursor-based, matching List emails.


Get deliverability insights

GET /v1/emails/:id/insights

Requires read_only scope. Runs a set of pre-send checks on the message and returns a deliverability score with the individual checks.

Response

{
  "score": 86,
  "checks": [
    {
      "id": "spf",
      "label": "SPF record",
      "status": "pass",
      "message": "The sending domain publishes an SPF record that authorizes Sendry.",
      "action": null
    },
    {
      "id": "subject",
      "label": "Subject length",
      "status": "warn",
      "message": "The subject line is longer than 60 characters and may be truncated.",
      "action": "Shorten the subject to 60 characters or fewer."
    }
  ],
  "generated_at": "2025-03-12T09:00:00Z"
}

Each check has a status of pass, warn, fail, or info.


GET /v1/emails/:id/share

Requires read_only scope. Returns a preview URL for the email that expires after 48 hours.

Response

{
  "url": "/preview/emails/V1StGXR8Z5jdHi6BmyT0fKc9QwErTy",
  "expires_at": "2025-03-14T09:00:00Z"
}

The returned url is relative. Prefix it with your dashboard origin to build the full link.


Cancel email

POST /v1/emails/:id/cancel

Requires sending_access scope. Cancels a scheduled or queued email. Returns an error if the email has already been sent.

Response

{
  "id": "email_abc123",
  "status": "cancelled"
}

On this page