Send a single transactional email through your workspace's verified sending domain. The send is logged and the email's ID is returned. To send many emails in one request, use Batch send; to read back a send's delivery status, use the Emails endpoint.
| Header | Value |
|---|---|
Authorization | Bearer ps_live_YOUR_API_KEY — required |
Content-Type | application/json — required |
JSON object with the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
to | string | required | Recipient email address. |
subject | string | required | Email subject line. |
html | string | required | HTML body of the email. |
sender_id | string | optional | ID of a sender configured in your workspace (Settings → Senders). Takes precedence over from. Must belong to your workspace and be on a verified domain, or the request is rejected (404 sender_not_found / 403 sender_not_verified). |
from | string | optional | Sender address on one of your verified domains (validated; 403 sender_not_verified otherwise). Ignored when sender_id is given. Omit both to use your workspace default sender. |
reply_to | string | string[] | optional | Reply-To address — a single email address or an array of addresses — set on the message. Precedence: this field, then the resolved sender's default reply_to (set per sender in Settings → Senders), then none. An invalid address is rejected as invalid_field (param reply_to) and nothing is sent. |
attachments | object[] | optional | Up to 20 attachments, each inline ({ filename, content (base64), content_type? }) or hosted ({ filename, url, content_type? }). See Attachments below. |
Example request body:
Reply-To. Pass reply_to as a single email or an array of emails to set the message's Reply-To. Precedence is request reply_to → the resolved sender's default reply_to → none; you can set a per-sender default under Settings → Senders. An invalid address fails with invalid_field (param: "reply_to") and the email is not sent.
Add up to 20 attachments. Each is either inline (base64 content) or hosted (a url we fetch at send time and discard — nothing is stored). Provide exactly one of content or url per attachment.
Host large files, attach small ones. Inline base64 is capped well below the request-body limit (a few MB) — for anything larger, host the file and pass a url. It's lighter on the wire and the recommended path for big files. See Deliverability for why.
Limits & errors. An oversized inline attachment, a disallowed file type, or an unreachable / disallowed URL is rejected with invalid_field (param: "attachments") and nothing is sent. A whole message past the provider ceiling (~38 MB encoded) is rejected with message_too_large (413). A large-but-sendable message succeeds and returns a soft warnings array nudging you to host the file instead.
On success the API returns 200 with a JSON body of success, id, and message. The id is the email's raw UUID — pass it to GET /api/v1/emails/{id} to read its delivery status.
On validation failure the API returns 400 with the standard error envelope (for example, a missing field):
Every response — success or error — also includes an X-Request-Id header. See Error codes for the canonical envelope and the full list of possible errors.
Testing? A ps_test_ key sends through the same path but captures instead of delivering — free, no reputation impact. See Test mode.
Every API key is limited to 1,000 requests per minute; over the limit you get a 429 rate_limited with a Retry-After header. To send in bulk, use Batch send rather than looping over /send. See Rate limits for the headers and handling.