POST https://www.ewyn.ai/api/v1/sendRequest body
| Field | Type | Required | Notes |
|---|---|---|---|
| to | string | yes | Recipient email address |
| subject | string | yes | Subject line; may contain {{variable}} placeholders |
| templateVersionId | uuid | one of | Published template version ID. templateId is accepted as a legacy alias — exactly one of the two is required |
| type | string | no | transactional or marketing. Omitted: the template's email type when set in the dashboard, else transactional. See Subscriptions and unsubscribe |
| topic | string | no | Subscription topic slug for a marketing send (for example newsletter). Omitted: the template's default topic, else general marketing. Not allowed with type: "transactional" |
| preheader | string | no | Preview text after the subject in most clients; supports {{variable}} placeholders |
| variables | object | no | String map substituted into {{ }} tokens in the template, subject, and preheader. Names starting with @ are reserved for system variables such as {{@unsubscribeUrl}} and are rejected with 400 |
| metadata | object | no | Attached to the message for your own correlation |
| idempotencyKey | string | no | Dedupes repeat requests for 24 hours |
| from | string | no | Override the workspace default sender address |
| fromName | string | no | Override the sender display name (max 255 chars) |
| replyTo | string | no | Override the workspace default reply-to |
| attachments | array | no | Up to 10 files to attach — see Attachments below |
Only mark email transactional when the recipient expects it as part of a service they use — receipts, confirmations, password resets. A transactional send is stopped only by a hard bounce or a manual suppression; a marketing send is also stopped by a spam complaint and by an opt-out from its topic or from all marketing.
Attachments
Each entry attaches one file. Provide exactly one of content (base64) or url (an https URL Ewyn downloads at send time, so your file server only needs to be reachable while the request is in flight).
| Field | Type | Required | Notes |
|---|---|---|---|
| filename | string | yes | Name shown to the recipient (max 255 chars) |
| content | string | one of | Standard base64 of the file bytes, no data: prefix |
| url | string | one of | https URL; private and internal addresses are refused |
| contentType | string | no | MIME type; inferred from the response or extension when omitted |
| contentId | string | no | Embeds the file inline; reference it in the template as <img src="cid:logo"> |
- 10 attachments per message.
- Inline
content: 2.5 MiB total (decoded). The whole request body must stay under 4 MB — useurlfor larger files. - 7 MiB total across all attachments, inline and fetched.
- Executables, scripts, HTML and SVG files are rejected with
ATTACHMENT_TYPE_BLOCKED. - Attachment bytes are retained for 6 months, then deleted.
- Idempotent replays return the original message; attachments on a replay are ignored (
attachmentsIgnoredin the response). Sends to suppressed recipients don't store attachments (attachmentsDroppedin the 422) and can't be resent from the dashboard.
Example
curl -X POST https://www.ewyn.ai/api/v1/send \
-H "Authorization: Bearer $EWYN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "maya@example.com",
"subject": "Welcome, {{firstName}}!",
"templateVersionId": "4f6f3a0e-9b1c-4f7a-8a3e-version-uuid",
"type": "transactional",
"variables": { "firstName": "Maya" },
"idempotencyKey": "signup-usr_8h2k4",
"attachments": [
{ "filename": "welcome.pdf", "content": "JVBERi0xLjQK...", "contentType": "application/pdf" },
{ "filename": "terms.pdf", "url": "https://files.yourdomain.com/terms.pdf" }
]
}'Response
{
"messageId": "msg_...",
"status": "queued",
"queuedAt": "2026-06-12T09:30:00.000Z",
"type": "transactional"
}queued means Ewyn accepted the message and handed it to the delivery queue. Delivery, open, click, and bounce events appear on the contact's journey timeline in the dashboard as your ESP reports them back.
type is the email type as the server resolved it — your type, else the template's. A marketing send also echoes topic when one applied (yours, or the template's default), so an integration can see what the server decided.
A repeated idempotencyKey within 24 hours returns the original messageId without sending again. If the original attempt terminally failed, the replay re-queues it instead. A replay of a send that was blocked (422) is judged again: once the recipient is no longer blocked — the opt-out or suppression was removed — the replay queues it and answers with status: "queued".
Blocked recipients
When the recipient must not receive this send, the message is recorded with a Suppressed status and the request fails with 422 RECIPIENT_SUPPRESSED. details.kind tells a delivery block from a marketing opt-out:
{
"code": "RECIPIENT_SUPPRESSED",
"error": "maya@example.com unsubscribed from topic \"newsletter\". Marketing email is not sent to them. ...",
"reason": "OPT_OUT_TOPIC",
"details": { "kind": "opt_out", "reason": "OPT_OUT_TOPIC", "topic": "newsletter" },
"type": "marketing",
"topic": "newsletter",
"messageId": "msg_..."
}| details.kind | details.reason | Blocks |
|---|---|---|
| suppression | HARD_BOUNCE | every send |
| suppression | MANUAL | every send |
| suppression | COMPLAINT | marketing sends |
| opt_out | OPT_OUT_TOPIC | marketing sends under that topic; details.topic names it |
| opt_out | OPT_OUT_ALL | every marketing send |
An opt-out never blocks a transactional send. If a service email is refused with kind: "opt_out", send it with type: "transactional".
Errors
| Status | Meaning |
|---|---|
| 400 | Validation failed — missing subject, invalid email, no template version ID, topic combined with type: "transactional", or a variable name starting with @ |
| 400 | ATTACHMENT_INVALID (bad base64, non-https url, blocked host, duplicate contentId) or ATTACHMENT_TYPE_BLOCKED. attachment.index names the offending file. |
| 401 | Missing or invalid API key |
| 404 | Template version not found in this workspace |
| 410 | ATTACHMENTS_EXPIRED — an idempotent replay of a failed send whose attachments were deleted after the retention period; send again with a new key |
| 413 | ATTACHMENT_TOO_LARGE (inline or total attachment limit) or REQUEST_TOO_LARGE (body over 4 MB) |
| 422 | RECIPIENT_SUPPRESSED — the recipient is blocked for this type of email; see Blocked recipients |
| 422 | TOPIC_NOT_FOUND — topic names no active topic in this workspace (unknown or archived) |
| 422 | ATTACHMENT_FETCH_FAILED — a url attachment returned an error, timed out, or was empty |
| 429 | Rate limited, QUOTA_EXCEEDED, or ATTACHMENT_QUOTA_EXCEEDED — see Errors & rate limits |