Sending Mail
The POST /v3/mail/send endpoint is ScaiSend's send API. It takes the same request shape SendGrid's /v3/mail/send takes — personalizations, from, subject, content, attachments, templates, tracking settings, suppression groups — and returns 202 Accepted with a message ID.
Endpoint: POST /v3/mail/send
Auth: API key or JWT, scope mail.send.
Response: 202 with {"message_id": "..."} (single personalization) or {"message_ids": [...]} (multiple).
Basic send#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Request fields (top-level)#
| Field | Type | Required | Notes |
|---|---|---|---|
personalizations |
array | Yes | 1–1000 entries; each creates one message |
from |
object | Yes | {email, name?}; email must use a verified sender domain (live mode) |
reply_to |
object | No | {email, name?} |
reply_to_list |
array | No | Multiple reply-to addresses |
subject |
string | Conditional | Required unless each personalization sets one, or a template supplies it |
content |
array | Conditional | Required unless template_id is set. Array of {type, value}; types are text/plain, text/html |
template_id |
string | Conditional | Must start with d-. Mutually exclusive with content |
attachments |
array | No | Max 10; total body size ≤ 20 MB |
headers |
object | No | Custom headers; string → string |
categories |
array | No | Up to 10 tags, each ≤ 255 chars |
custom_args |
object | No | Arbitrary key-value metadata, preserved in message record and events |
send_at |
integer | No | Unix timestamp; scheduled delivery. Requires mail.schedule scope |
batch_id |
string | No | Group messages for GET /v3/mail/batch/{id} status aggregation |
asm |
object | No | {group_id, groups_to_display?} — unsubscribe group membership |
ip_pool_name |
string | No | Named outbound IP pool (if configured) |
mail_settings |
object | No | sandbox_mode, bypass_*, footer |
tracking_settings |
object | No | Open / click / subscription / GA tracking overrides |
Personalizations#
Each entry in personalizations becomes exactly one message. If you have two entries, ScaiSend sends two emails (one per entry) and returns {"message_ids": [...]}.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Per-personalization fields:
| Field | Type | Notes |
|---|---|---|
to |
array | Required, 1–1000 recipients |
cc |
array | Optional |
bcc |
array | Optional |
subject |
string | Overrides top-level subject |
headers |
object | Overrides top-level headers |
substitutions |
object | Legacy SendGrid key-value substitutions (use dynamic_template_data for modern templates) |
dynamic_template_data |
object | Data passed to the template engine |
custom_args |
object | Overrides top-level custom_args |
send_at |
integer | Per-personalization schedule override |
Use one personalization per recipient when each recipient should get different data. Use multiple recipients in one personalization's to when they're all getting the same message.
Templates#
Skip content and set template_id to send a dynamic-template email:
1 2 3 4 5 6 7 8 9 10 | |
The active version of d-receipt renders with dynamic_template_data as its context. See Templates for the full template workflow.
Attachments#
Attachments are base64-encoded in the JSON body. Max 10 per message; total body size ≤ 20 MB.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
| Attachment field | Required | Notes |
|---|---|---|
content |
Yes | Base64-encoded file bytes |
filename |
Yes | 1–255 chars |
type |
No | MIME type; defaults to application/octet-stream |
disposition |
No | attachment (default) or inline |
content_id |
No | Required for disposition: inline; used to reference from HTML (<img src="cid:...">) |
For inline images (a logo embedded in an HTML signature, for example):
1 2 3 4 5 6 7 8 9 10 11 | |
Then reference in HTML: <img src="cid:logo" />.
See Attachments and Images for the full discussion including the image library approach (preferred when reusing images across sends).
Scheduled delivery#
Set send_at to a future Unix timestamp:
1 2 3 4 5 6 7 | |
The message is accepted immediately (202), sits in QUEUED state until the send_at time, then proceeds through rendering and delivery. Requires mail.schedule scope. Cancel before it sends with POST /v3/messages/{id}/cancel (requires mail.cancel).
Categories#
Categories are arbitrary tags you attach for reporting. A message can have up to 10.
1 2 3 | |
Then query stats by category:
1 2 | |
Keep the category vocabulary small and stable — ad-hoc tags per send make stats useless.
Custom args#
custom_args (top-level or per-personalization) is arbitrary JSON metadata. It's stored on the message record, returned on GET /v3/messages/{id}, and included in every event webhook that references the message. Use it for your own IDs (user ID, order ID, campaign ID) so you can correlate ScaiSend events back to your system.
1 2 3 4 5 6 7 | |
Tracking settings#
Per-request override of tenant defaults. See Tracking for the full discussion.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Mail settings#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
See Sandbox vs Live for sandbox_mode and Suppressions for the bypass flags.
Response#
Single personalization#
HTTP 202 Accepted:
1 | |
Multiple personalizations#
HTTP 202 Accepted:
1 | |
Track each with GET /v3/messages/{id}.
Error cases#
| Code | Response | Cause |
|---|---|---|
| 400 | errors[] with field: "template_id" |
Template ID doesn't start with d- |
| 400 | errors[] with field: "personalizations" |
Empty, too many, or missing required nested fields |
| 400 | errors[] with field: "from" |
Invalid from address |
| 400 | errors[] with field: "content" or field: "template_id" |
Both supplied (mutually exclusive), or neither |
| 400 | errors[] with field: "attachments[N]" |
Base64 decode failed, attachment too large, wrong MIME |
| 401 | {"detail": "Invalid API key"} |
Key revoked, deleted, or typo'd |
| 403 | {"detail": "Missing required scope: mail.send"} |
Key or user role lacks the scope |
| 403 | {"detail": "Sender domain not verified"} |
from.email domain isn't a verified sender for this tenant |
| 413 | {"detail": "Request body exceeds maximum size of 20 MB"} |
JSON body over 20 MB (usually attachments) |
| 422 | Pydantic validation detail | Schema mismatch |
| 429 | {"detail": "Rate limit exceeded"} |
Per-tenant rate limit hit; honor Retry-After |
Batch status#
When sending many messages with the same batch_id:
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
See Scheduling and Batches for the batching workflow.
What's next#
- Templates — dynamic templates and the Handlebars syntax.
- Attachments and Images — inline images, the image library.
- Mail Send Reference — exhaustive field-level reference.