Skip to main content
Email supports transactional sends, hub-specific sending configuration, templates, drip campaigns, suppressions, webhooks, and unsubscribe.

Configure hub sending

Email templates

These endpoints require an authenticated team context. Send X-Team-ID to select a team; without that header, a user token’s team claim is used. An empty X-Team-ID is rejected with 400. The caller must still belong to the selected team, and an API key is limited to its own team. The hub must belong to the selected team. A template from another hub, or a deleted template, is returned as 404. Create a template with a JSON:API document. Only name, subject, description, mjml_source, and plain_text are writable; fields such as version, compiled_html, hub_id, and timestamps are server-managed. The 201 response includes a Location header for the created template.
MJML and Jinja content is checked when a template is created and when a content field is changed. Invalid MJML or Jinja syntax, an unsupported variable, or disallowed template behavior returns 422 and stores no change. Templates can use the variables available to the email send context, such as contact_first_name, contact_last_name, contact_email, and hub_name. mjml_source is the MJML body; when it is empty, plain_text is used as the body instead. PATCH is partial: omitted fields remain unchanged, and description or plain_text can be cleared with null. name and subject are required on create. Names are limited to 200 characters, subjects to 255 characters and one line, descriptions to 2,000 characters, and each body field to 500,000 characters. Unknown or server-managed attributes are rejected with 422. If a PATCH body includes data.id, it must match the template ID in the URL or the request returns 409 resource_id_mismatch; "id": null is a 422, so leave id out instead. Listing supports cursor pagination with page[size] and page[after]. Steps can link a database template through template_id or a UUID template_ref; new and updated UUID references must point to an active template in the same hub. Non-UUID template names keep their existing resolution. Deleting a template returns 204; deletion returns 409 email_template_in_use while a step in a non-deleted drip campaign still references it. Replace references in active drip steps and automation nodes before deleting a template. For a UUID send that references a soft-deleted template, the worker allows rendering only when the persisted EmailSend.created_at is strictly earlier than EmailTemplate.deleted_at; equal, later, or missing send timestamps fail closed. In PostgreSQL, drip sends use CURRENT_TIMESTAMP (transaction-start time), while template deletion stamps deleted_at with the backend’s datetime.now(UTC). These persisted values can come from different clocks, so the comparison is not proof of causal ordering between queueing and deletion. Preview takes no request body and returns rendered HTML and subject text using sample contact and hub values.
Invalid MJML in a preview request now returns 422 invalid_template_mjml (previously returned 500). Update error handling to check for 422 with code invalid_template_mjml when previewing templates.

Create a drip campaign

Use drip campaigns for welcome sequences, content drip, cart recovery, post-purchase nurture, and reactivation.

Campaign and step counts

GET /v1/hubs/{hub_id}/drip-campaigns and GET /v1/hubs/{hub_id}/drip-campaigns/{campaign_id} return four counts on each campaign, computed when you read them: email_count (steps), enrolled_count (everyone ever enrolled, whatever their status), sent_count and open_count. GET .../steps returns sent_count and open_count for each step. A campaign or step with none returns 0. sent_count is the number of sends recorded as sent for that campaign or step. open_count is the number of those sends with a recorded open, counted once per send however often it was opened. Opens are recorded only when open tracking reports them, so 0 means none were reported, not that none happened. The create, update, activate and pause responses do not include the counts; the duplicate response does (all 0 on the new draft).

Duplicate a campaign as a draft

Takes no body and returns the new campaign (201). Only the owner of the selected team can duplicate (an API key bound to that team counts as the owner); any other member of the team gets 403. Any campaign can be duplicated, whatever its status. The copy is always a draft named "<name> (copy)", with the same description, entry rule, segment, re-enrollment setting and check interval, and every step in order with its timing, template and subject. Templates are shared with the original, not copied. Nobody is enrolled in the copy and nothing is sent until you activate it. The route returns 403 for a non-owner member, 404 for a campaign that does not exist or is not in that hub, 422 byo_drip_unsupported on a hub that uses its own SMTP, 422 invalid_segment_id if the campaign’s segment has been deleted, and 422 drip_step_template_missing if a step’s template no longer exists. Nothing is created in any of these cases.

Suppressions and unsubscribe

Suppression endpoints help prevent sends to blocked or unsubscribed addresses:
Email routes currently remain under /v1/... instead of /api/v1/.... Treat the generated API reference as the exact path source for email until those routes are folded into the canonical API prefix.

Drip admin enrollment

Administrators can enroll a contact, list a campaign’s enrollments, and exit an enrollment. Campaign and step authoring routes are also available; see the generated API reference for their exact request and response schemas.

Recover a failed drip enrollment

When a permanent send failure pauses an enrollment, fix the template or delivery issue before retrying. These recovery routes use the existing team-scoped drip access gate. A retry checks the recipient’s current suppression and membership status and renders the current step again. A successful retry creates a replacement send while preserving the failed send in the ledger. If the step should not be sent, skip it to advance the campaign or complete the enrollment.
The per-enrollment retry and skip routes take no request body and return the updated drip_enrollments JSON:API resource. They operate on enrollments paused for a failed send; when the enrollment eligibility check is reached, other states return 409 enrollment_not_send_failed. Retry and bulk retry are available only while the campaign is active. A draft, paused, or archived campaign returns 409 drip_campaign_not_active before retry changes enrollment or send state or queues a send. Skip is not gated by campaign status: it remains available for an otherwise eligible send-failed enrollment in draft, active, paused, or archived campaigns, and it does not enqueue a send. An individual retry returns 409 recipient_suppressed if the recipient is suppressed or is no longer an active team contact, 422 template_render_error if the current template cannot render, 422 byo_drip_unsupported for BYO-SMTP routing, 409 email_pool_unavailable when the hub has no email pool, 409 drip_send_conflict when the step has a live send, or 422 drip_step_missing when the enrollment’s current step does not exist. These preflight failures occur before enrollment/send updates or enqueue. In bulk retry, a per-enrollment preflight failure is counted as skipped_still_failing (suppression is counted as skipped_suppressed), while other enrollments in the batch may still retry. Fix the underlying condition before retrying again, or skip the failed step. The bulk route examines at most 50 failed enrollments per call, including ones that cannot be retried. Omit the body to examine failures from every campaign step. To filter by a step in this campaign, send the JSON:API meta-only body:
The response is meta-only. retried, skipped_still_failing, and skipped_suppressed count the outcomes in the batch; limit is 50 and has_more indicates that more failures remain.
If every item in a batch is still failing, fix or skip those enrollments before calling the bulk route again so later failures can be reached. A malformed step_id fails UUID request validation with a generic 422; a well-formed UUID that does not identify a step in this campaign returns 422 invalid_step_id.

Admin deliverability KPI

A per-hub email deliverability summary is available to team owners. This is distinct from the existing rolling member stats endpoint (GET /v1/hubs/{hub_id}/email-stats, hub-admin only, 24-hour rolling window).
Authentication: requires a team-owner JWT (not a contact/member JWT). Query parameters: Response (200):
Deliverability endpoint status: