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. One cause is an unresolved placeholder in the rendered subject or body:
An unresolved placeholder is a bare «…» token (the guillemet characters U+00AB … U+00BB) left in the content — for example, the Smart Suggester inserted «first_name» and it was never filled in. A «…» is not a valid NLT expression: it would ship to the recipient as literal «first_name» garbage, so Synapse refuses to send it. The email log's error_message reads unresolved_placeholder; retrieve it via the Email Logs detail endpoint.
The placeholder 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. Fill in every «…» (use {the user's first_name} and the like) before sending. See NLT variables for the correct syntax.
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) |