When to use individual sending vs. broadcasts
Use individual sending when you need a real-time response, want full per-message control, or are sending event-driven mail (one message per user action). Use broadcasts when you have a large list and want Helo to handle the delivery pipeline for you.Sending a message
Direct content
Use this for simple messages where the content is fully known ahead of time. Example requestcURL
Template content
Use this when you want to populate content dynamically using{{variable}} placeholders. Move your subject, HTML, and text into a template object alongside a data map.
Request
template, top-level subject, html, and text fields must not be included — pick one approach or the other.
Response
status is one of:
| Value | Meaning |
|---|---|
accepted | Message accepted and queued for delivery. |
delayed | Transient error during submission. Helo will retry automatically. |
failed | Validation or account error. The response includes an errorCode and errorMessage. |
messageId you can use for support lookups.
If some recipients were on your suppression list, they appear in the suppressions array — the message still sends to the remaining recipients.
errorCode: "recipients_suppressed" rather than sending to no one.
Batch sending
cURL
Idempotency
To prevent duplicate sends when retrying a failed request, include an idempotency key:Suppressions
Helo automatically checks every recipient against your channel’s suppression list before sending. Suppressed addresses (from previous bounces, unsubscribes, or manual additions) are silently removed from the To, Cc, and Bcc fields, and their addresses are returned in thesuppressions array on the response.
If you need to check whether an address is suppressed before submitting, use the Suppressions API.
Tracking
By default, open and link tracking follow your channel settings. Override them per-message with thetracking object:
Validation errors
If the API returns"status": "failed", the response includes an errorCode:
| Error code | Cause |
|---|---|
channel_not_found | The channel does not exist or you don’t have access to it. |
domain_unverified | The sender domain has not been verified on your account. |
account_forbidden | Your account is not permitted to send (blocked, rejected, or cancelled). |
recipient_forbidden | Pending accounts can only send to addresses on the same domain as the sender. |
recipients_suppressed | All recipients are on the suppression list. |
templating_error | A {{variable}} placeholder or syntax error was found in the template. |
send_limit_exceeded | Pending accounts have a 1,000-email free-send limit. |
Channel selection
If your API credential is scoped to a specific channel, no extra configuration is needed. If you’re using an account-level credential, specify the channel in the request header:Limits at a glance
| Limit | Value |
|---|---|
Recipients per message (to + cc + bcc) | 50 |
| Subject line length | 256 characters |
| Email address length | 254 characters |
| Tags per message | 5 |
| Tag length | 100 characters |
| Metadata fields | 10 |
| Metadata key length | 50 characters |
| Metadata value length | 100 characters |
| Total header size | 5,000 characters |
| Idempotency key length | 36 characters |
| Idempotency window | 1 hour |
| Free-send limit (Pending accounts only) | 1,000 emails |