Direct Send API
Send a transactional email immediately without creating a flow. Ideal for one-off emails like OTP codes, password resets, payment receipts, and order confirmations.
Endpoint
Headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | application/json |
X-WORKSPACE-ID | Yes | Your workspace identifier |
X-API-KEY | Yes | A data-scoped API key |
Request Body
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
template_slug | string | Yes | The slug of the email template to render |
to.user_id | string | Yes | The contact's external identifier. Used to upsert the contact record. |
to.email | string | Yes | The recipient's email address. |
to.first_name | string | No | First name for NLT personalization. |
to.last_name | string | No | Last name for NLT personalization. |
attributes | object | No | Data available in NLT templates as trigger event attributes |
idempotency_key | string | No | Prevents duplicate sends |
subject | string | No | The subject for this send. Highest-priority source in the subject resolution chain — when set, it wins over the template's stored subject. When omitted, Synapse falls back to the template's stored subject, then to an empty subject. |
from_email | string | No | Overrides the sender address for this send. |
sender_name | string | No | Overrides the sender display name for this send. |
reply_to | string | No | Sets the Reply-To address for this send. |
bcc | string[] | No | BCC recipients for this send. |
content_type | string | No | transactional or promotional. Controls CAN-SPAM footer behavior — see CAN-SPAM footer and suppression. Defaults to transactional (no footer) when omitted. |
Both to.user_id and to.email are required. The contact is upserted by user_id with the provided email and name fields.
Response
The response always contains exactly two fields:
| Field | Type | Description |
|---|---|---|
email_log_id | UUID | The email log record created for this send. Query it via the Email Logs API. |
status | string | sent, failed, or suppressed. |
Sent (200 OK)
Suppressed (200 OK)
status is suppressed when Synapse deliberately does not send the email — the log record is still written for audit. This happens when:
- A
requiredNLT expression in the template resolves to null (a required attribute is missing), or - The send is commercial but your workspace has no complete company address (see CAN-SPAM footer and suppression).
The specific suppression reason is recorded on the email log's error_message (for example, suppressed:missing_company_address). Retrieve it via the Email Logs detail endpoint.
Failed (200 OK)
status is failed when the send reached a terminal state without delivering — the log record is still written for audit. Two causes are checked, in order:
1. Unresolved placeholder
An unresolved placeholder is malformed personalization syntax left in the content that would otherwise ship to the recipient as literal garbage. Synapse refuses to send it. Two kinds are caught:
- A bare
«…»token (the guillemet characters U+00AB … U+00BB) — for example, the Smart Suggester inserted«first_name»and it was never filled in. A«…»is not a valid NLT expression. This is caught in both the rendered subject and the rendered body. - An unclosed
{brace in an NLT expression — for example, a body containingHi {the user's first namewith no closing}. This would leak the raw{…}fragment to the recipient. It is caught in the rendered subject and the rendered body.
Either kind sets the email log's error_message to unresolved_placeholder; retrieve it via the Email Logs detail endpoint.
The «…» guard also decodes HTML entity forms of the guillemets before scanning — «/» (with or without the trailing ;) and numeric character references such as « / « all count as a placeholder, because a mail client would render them as a real guillemet. Always close every NLT expression's brace and fill in every «…» (use {the user's first_name} and the like) before sending. See NLT variables for the correct syntax.
Well-formed, balanced braces in an email HTML body — CSS rules like .btn{color:#1D9E75}, @media queries, or JSON-LD <script> blocks — are never mistaken for a placeholder and never block a send. Only an unclosed brace in the body is caught there.
2. Empty subject
Checked only after the placeholder check passes. If the resolved subject is empty or whitespace-only after the full subject resolution chain (request subject → template's stored subject), Synapse fails the send closed rather than deliver an email with a blank Subject: header:
Sets the email log's error_message to render_suppressed:empty_subject; retrieve it via the Email Logs detail endpoint.
An explicit "subject": "" in the request body does not count as a deliberate override that stops the chain — it's treated the same as omitting subject entirely, and falls through to the template's stored subject. Only a non-empty string, in either the request or the template, resolves the chain. Set a subject on the request or the template to avoid this.
Error Responses
| Status | Code | Description |
|---|---|---|
400 | validation_error | Missing required fields |
422 | literal_error | A field failed schema validation — for example, content_type set to a value other than transactional or promotional |
401 | invalid_api_key | Invalid or missing API key |
403 | insufficient_scope | API key lacks data scope |
404 | template_not_found | Template slug does not exist or is inactive |
404 | contact_not_found | No contact found with the given external_id (and no email fallback) |
409 | duplicate_send | Idempotency key already used |
429 | rate_limit_exceeded | Rate limit exceeded |
Examples
OTP Verification
Python
JavaScript
Priority Routing
Direct send emails are routed through the priority queue system based on the template's content_type:
| Content Type | Priority Queue | Use Case |
|---|---|---|
| OTP/2FA templates | q.email.critical | Never delayed |
| Transactional | q.email.high | Delivered within seconds |
| Promotional | q.email.low | May be batched |
For OTP and password reset emails, use direct send with a transactional template. These are routed to the critical priority queue with dedicated workers for sub-second delivery.
CAN-SPAM footer and suppression
Synapse enforces CAN-SPAM at send time. The content_type field decides whether a send is treated as commercial. It accepts only transactional, promotional, or omission — any other value is rejected with 422 validation_error.
content_type | Treated as | Footer + List-Unsubscribe |
|---|---|---|
transactional | Transactional | No — exempt |
| (omitted) | Transactional | No — exempt |
promotional | Commercial | Yes — footer + one-click unsubscribe |
| (any other value) | — | Rejected with 422 before sending |
/v1/send is the transactional send surface, so an omitted content_type is treated as transactional and stays exempt. Send promotional for genuinely commercial mail.
What a commercial send adds
When a send is commercial, Synapse automatically:
- Appends a footer containing your workspace's physical mailing address and an unsubscribe link, immediately before the closing
</body>tag. - Adds
List-UnsubscribeandList-Unsubscribe-Postheaders for one-click unsubscribe (see the Unsubscribe API).
You do not build or template any of this — pass content_type: "promotional" and Synapse injects it.
Fail-closed on a missing address
A commercial send from a workspace with no complete company address is suppressed — the response status is suppressed and no email is sent. A commercial email without a physical mailing address is itself a CAN-SPAM violation, so Synapse refuses to send it rather than ship a non-compliant message.
The required fields are your company name, address line 1, city, postal code, and country. Set them in Settings → General before sending commercial email. Transactional sends are never blocked by this check.
The email log's error_message will read suppressed:missing_company_address.
Direct Send vs Flows
| Concern | Direct Send | Flows |
|---|---|---|
| Trigger | Explicit API call | Automatic on matching event |
| Timing | Immediate | Can include wait steps |
| Branching | Single email | Multi-step with conditions |
| Use case | OTP, receipts, alerts | Onboarding, follow-ups, campaigns |
| Idempotency | Per-send key | Per-flow-trip (automatic) |