Mail Send Reference
The /v3/mail/send endpoint and related batch endpoints. For the guide-level walk-through, see Sending Mail.
Base path: /v3/mail/
Required permission: mail.send (send), mail.schedule (batch endpoints, send_at), mail.cancel (cancel scheduled).
POST /v3/mail/send#
Queue one or more messages for delivery.
Request body:
Top-level fields:
| Field | Type | Required | Notes |
|---|---|---|---|
personalizations |
array | Yes | 1–1000 items; each creates one message |
from |
object | Yes | {email, name?}; must use verified domain (live only) |
reply_to |
object | No | {email, name?} |
reply_to_list |
array | No | Alternative to single reply_to |
subject |
string | Conditional | Required unless each personalization has one, or template supplies it |
content |
array | Conditional | Required unless template_id is set |
template_id |
string | Conditional | Must start with d-; mutually exclusive with content |
attachments |
array | No | Max 10 items; total body ≤ 20 MB |
headers |
object | No | String → string |
categories |
array | No | Max 10 items, each ≤ 255 chars |
custom_args |
object | No | Arbitrary metadata |
send_at |
integer | No | Unix timestamp; future schedule. Requires mail.schedule |
batch_id |
string | No | From POST /v3/mail/batch |
asm |
object | No | {group_id, groups_to_display?} |
ip_pool_name |
string | No | Named outbound IP pool |
mail_settings |
object | No | See below |
tracking_settings |
object | No | See below |
Personalization fields:
| Field | Type | Required | Notes |
|---|---|---|---|
to |
array of {email, name?} |
Yes | 1–1000 recipients |
cc |
array of {email, name?} |
No | |
bcc |
array of {email, name?} |
No | |
subject |
string | No | Overrides top-level |
headers |
object | No | Overrides top-level |
substitutions |
object | No | Legacy SendGrid substitutions |
dynamic_template_data |
object | No | Template rendering context |
custom_args |
object | No | Overrides top-level |
send_at |
integer | No | Per-personalization schedule |
Content fields:
| Field | Type | Required | Notes |
|---|---|---|---|
type |
string | Yes | text/plain or text/html |
value |
string | Yes | Body content |
Attachment fields:
| Field | Type | Required | Notes |
|---|---|---|---|
content |
string (base64) | Yes | File bytes |
filename |
string | Yes | 1–255 chars |
type |
string | No | MIME type |
disposition |
string | No | attachment (default) or inline |
content_id |
string | Conditional | Required for inline |
mail_settings fields:
| Field | Type | Notes |
|---|---|---|
sandbox_mode.enable |
bool | Force sandbox for this request |
bypass_list_management.enable |
bool | Ignore all suppression lists |
bypass_bounce_management.enable |
bool | Ignore bounce suppressions |
bypass_spam_management.enable |
bool | Ignore spam-report suppressions |
bypass_unsubscribe_management.enable |
bool | Ignore unsubscribe suppressions |
footer.enable |
bool | Add a footer |
footer.text |
string | Plain-text footer |
footer.html |
string | HTML footer |
tracking_settings fields:
| Field | Type | Notes |
|---|---|---|
click_tracking.enable |
bool | |
click_tracking.enable_text |
bool | Also rewrite plain-text URLs |
open_tracking.enable |
bool | |
open_tracking.substitution_tag |
string | Custom pixel placement |
subscription_tracking.enable |
bool | |
subscription_tracking.text |
string | Plain-text unsubscribe footer |
subscription_tracking.html |
string | HTML unsubscribe footer |
subscription_tracking.substitution_tag |
string | Custom placement |
ganalytics.enable |
bool | |
ganalytics.utm_source |
string | |
ganalytics.utm_medium |
string | |
ganalytics.utm_campaign |
string | |
ganalytics.utm_term |
string | |
ganalytics.utm_content |
string |
Response (202 Accepted):
Single personalization:
1 | |
Multiple personalizations:
1 | |
Error responses:
| Status | Shape | When |
|---|---|---|
| 400 | {"errors": [...]} |
Validation — missing required field, conflict between content and template_id, invalid from, etc. |
| 401 | {"detail": "..."} |
Missing or invalid credentials |
| 403 | {"detail": "Missing required scope: mail.send"} |
Key lacks scope |
| 403 | {"detail": "Sender domain not verified"} |
from.email domain unverified (live only) |
| 413 | {"detail": "Request body exceeds maximum size of 20 MB"} |
Body too large |
| 422 | {"detail": [...]} |
Pydantic schema error |
| 429 | {"detail": "Rate limit exceeded"} |
Honor Retry-After header |
POST /v3/mail/batch#
Create a new batch ID.
Request: empty body.
Response (201):
1 | |
Permission: mail.send (creating IDs isn't restricted to mail.schedule).
GET /v3/mail/batch/{batch_id}#
Aggregate status for every message with this batch_id.
Response (200):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
is_complete flips to true when every message has reached a terminal state.
Errors: 404 if batch_id has no messages or belongs to a different tenant.