Limits and errors
Rate limits, size limits, plan limits, sending reputation and every error code the Email API returns.
Rate limit
Each API key may make 10 requests per second. The allowance is a bucket of 10 that refills continuously, so a short burst is fine and a brief pause restores it.
Every response carries:
| Header | Meaning |
|---|---|
x-ratelimit-limit | Requests allowed per second (10). |
x-ratelimit-remaining | Requests left in the bucket right now. |
retry-after | On a 429 only: seconds to wait before retrying. |
Over the limit, the API answers 429 with code RATE_LIMITED:
{ "success": false, "error": "Rate limit exceeded: 10 requests per 1s. Retry in 1s.", "code": "RATE_LIMITED" }A batch of 50 messages is one request, so batching is the way to send faster.
Size and count limits
| What | Limit |
|---|---|
Recipients per message (to + cc + bcc, after duplicates are removed) | 50 |
Entries in each of to, cc, bcc | 1 to 50 |
| Messages per batch | 50 |
| Recipients per batch, all messages together | 50 |
html, text per message | 500,000 characters each |
subject | 500 characters |
Address fields (from, replyTo, each recipient) | 320 characters |
idempotencyKey | 128 characters |
| Attachments | not supported |
Template name | 63 characters (letters, digits, _, -) |
Template htmlBody, textBody | 200,000 characters each |
Broadcast body | 500,000 characters |
Contacts per POST /contacts | 1,000 |
| Custom properties stored per call | 50 (keys up to 100 characters) |
| CSV import | 10,000,000 characters; first 5,000 rows are read |
| CSV export | 50,000 contacts |
| Segment filter rules | 20 |
Topic ids per PUT /contacts/{id}/topics | 50 |
Webhook url | 2,000 characters, https:// only |
Webhook events | 20 entries |
| Inbox bulk update | 500 ids |
| Forwarding addresses per mailbox | 5 |
Page size (limit) | emails, contacts, inbox messages: 1 to 200 (default 50); suppressions: 1 to 500 (default 100); broadcast recipients: 1 to 500 (default 100) |
Plan limits
Your plan sets how many emails you can send per day and per month, and how many domains, templates, contacts, segments, topics, webhooks, mailboxes and aliases the project can have, and whether broadcasts and forwarding are included. Read the current values from GET /status (plan) and GET /inbound/stats.
- Quota is counted per recipient and reserved when a message is accepted. Rejected sends do not count.
- The daily limit (on plans that have one) resets at 00:00 UTC.
- The monthly window starts on your subscription date.
- Past the monthly limit, sending continues only with pay-as-you-go enabled. See Quota and billing.
Hitting a plan limit returns 402 with a specific code (for example DOMAIN_LIMIT_REACHED). Upgrade the plan in the dashboard, or remove something you no longer need.
Reputation
Bounces and spam complaints hurt the reputation that your mail is delivered on. Helipod Email watches each project's rolling rates:
- Bounce rate above 2% or complaint rate above 0.08% (the exact limits for your project are in
GET /analyticsunderlimits) suspends sending for the project: sends return403 REPUTATION_SUSPENDED. The suspension lifts automatically once the rates are back under the limits, or after the project has been quiet for a while. - If the combined reputation of all senders is at risk, sending can be paused for every project: sends return
403 PLATFORM_REPUTATION, andplatformPausedinGET /statusexplains why.
Keep lists clean: send only to people who asked for your mail, remove addresses that bounce, and make unsubscribing easy.
Errors
Every error uses the same envelope:
{ "success": false, "error": "Human readable message", "code": "MACHINE_CODE" }Branch on code. Messages are meant for people and can change.
HTTP status codes
| Status | Meaning |
|---|---|
200, 201 | OK, created. |
202 | Accepted. The work continues in the background (a jobId is returned). |
400 | The request broke a rule. See the code. |
401 | Missing, invalid or expired API key. |
402 | Plan, quota or balance limit. |
403 | Not allowed right now: unverified domain, suppressed recipient, or sending suspended. |
404 | Not found. |
409 | Conflict: already exists, or the resource is in the wrong state. |
422 | The request body or query does not match the schema (VALIDATION). |
429 | Rate limited. |
500 | Unexpected error. Retry with backoff; use idempotencyKey on sends. |
503 | Temporarily unavailable. Retry shortly. |
Authentication and request
| Code | Status | Meaning |
|---|---|---|
NO_API_KEY | 401 | No Authorization: Bearer header, or an empty key, on a send endpoint. |
UNAUTHORIZED | 401 | No Authorization: Bearer header on any other endpoint. |
INVALID_API_KEY | 401 | The key does not exist or was revoked. |
API_KEY_EXPIRED | 401 | The key's expiry date has passed. |
NO_PLAN | 402 | The project has no active email plan. Reading the status and plan, and buying or renewing a plan, still work. |
KEY_NOT_ALLOWED | 403 | The route stays in the dashboard: email keys, cancelling the plan, its billing switches, mailbox passwords. |
IDEMPOTENCY_KEY_REQUIRED | 400 | A plan purchase with a key was sent without an Idempotency-Key header. See Plan and add-ons. |
NO_CONFIG | 404 | The key's email configuration no longer exists. |
EMAIL_NOT_SETUP | 403 | The email service is not activated for the project. |
PROJECT_NOT_FOUND | 404 | The project does not exist. |
VALIDATION | 422 | A body, query or path field is missing or invalid. The message names the field. |
RATE_LIMITED | 429 | Too many requests. See retry-after. |
NOT_FOUND | 404 | No such route. |
INTERNAL_ERROR | 500 | Unexpected error. |
Sending
| Code | Status | Meaning |
|---|---|---|
NO_RECIPIENTS | 400 | to is empty. |
INVALID_RECIPIENT | 400 | An address is malformed or uses a reserved domain. The message lists each one with the reason. |
RECIPIENT_DOMAIN_UNDELIVERABLE | 400 | A recipient's domain does not exist or cannot receive mail. |
TOO_MANY_RECIPIENTS | 400 | More than 50 recipients in a message or in a batch. |
EMPTY_BODY | 400 | None of html, text or templateId. |
SUBJECT_REQUIRED | 400 | No subject on a send without a template. |
INVALID_HEADER | 400 | A line break in subject, from or replyTo, or an invalid custom header. |
INVALID_FROM | 400 | from is not a valid address. |
ATTACHMENTS_NOT_SUPPORTED | 400 | attachments was sent. |
INVALID_COMBINATION | 400 | templateId combined with custom headers. |
MISSING_VARIABLES | 400 | A {{token}} has no value in variables or templateData. |
VARIABLES_REQUIRED | 400 | The content has {{tokens}} but no variables were sent. |
NO_MESSAGES | 400 | A batch with no messages. |
TOO_MANY_MESSAGES | 400 | A batch with more than 50 messages. |
BATCH_FAILED | 400 | Every message of a batch failed; per-message results are in data. |
TEMPLATE_NOT_FOUND | 404 | templateId is not a template of this project. |
DOMAIN_NOT_FOUND | 403 | The from domain is not added to the project. |
DOMAIN_NOT_VERIFIED | 403 | The from domain is not verified yet. |
RECIPIENT_SUPPRESSED | 403 | A recipient is suppressed, or previously bounced or complained. |
REPUTATION_SUSPENDED | 403 | Sending is suspended for the project because of its bounce or complaint rate. |
PLATFORM_REPUTATION | 403 | Sending is paused for every project. |
TENANT_PAUSED | 403 | Sending is paused for the project by the delivery network. Contact support. |
SENDING_DISABLED | 403 | Sending is turned off for the project. |
DAILY_QUOTA_EXCEEDED | 402 | The daily limit is reached. |
MONTHLY_QUOTA_EXCEEDED | 402 | The monthly limit is reached and the plan has no pay-as-you-go rate. |
PAYG_DISABLED | 402 | The monthly limit is reached and pay-as-you-go is off. |
INSUFFICIENT_BALANCE | 402 | Pay-as-you-go needs more wallet deposit. |
WALLET_NOT_FOUND | 404 | No wallet for pay-as-you-go. |
PLAN_NOT_FOUND | 404 | The active plan could not be loaded. |
In a batch, a rejected message can also carry code: "UNKNOWN" for an unexpected per-message error.
Domains
| Code | Status | Meaning |
|---|---|---|
INVALID_DOMAIN | 400 | Not a valid domain name. |
DOMAIN_EXISTS | 409 | Already added to this project. |
DOMAIN_LIMIT_REACHED | 402 | The plan allows no more domains. |
DOMAIN_NOT_FOUND | 404 | No such domain. |
Templates
| Code | Status | Meaning |
|---|---|---|
INVALID_TEMPLATE_NAME | 400 | Name has characters other than letters, digits, _ and -, or is too long. |
EMPTY_TEMPLATE | 400 | Neither htmlBody nor textBody. |
TEMPLATE_LIMIT_REACHED | 402 | The plan allows no more templates. |
TEMPLATE_NOT_FOUND | 404 | No such template. |
Contacts, segments and topics
| Code | Status | Meaning |
|---|---|---|
CONSENT_REQUIRED | 400 | A bulk add or import without consentConfirmed: true. |
INVALID_EMAIL | 400 | A contact address is not valid. |
DISPOSABLE_EMAIL | 400 | A contact uses a disposable inbox provider. |
RECIPIENT_DOMAIN_UNDELIVERABLE | 400 | A contact's domain cannot receive mail. |
EMPTY_REQUEST | 400 | No contacts in the request. |
TOO_MANY | 400 | More than 1,000 contacts in one request. |
EMPTY_CSV | 400 | The CSV has no header or no data rows. |
CSV_MISSING_EMAIL | 400 | The CSV header has no email column. |
NO_VALID_CONTACTS | 400 | Every CSV row was rejected. |
INVALID_CURSOR | 400 | cursor is not a value returned by the previous page. |
RECIPIENT_SUPPRESSED | 403 | A contact address is on the suppression list. |
CONTACT_LIMIT_REACHED | 402 | The plan's contact limit would be exceeded. |
SEGMENT_LIMIT_REACHED | 402 | The plan allows no more segments. |
TOPIC_LIMIT_REACHED | 402 | The plan allows no more topics. |
CONTACT_NOT_FOUND | 404 | No such contact. |
SEGMENT_NOT_FOUND | 404 | No such segment. |
TOPIC_NOT_FOUND | 404 | No such topic, or an unknown id in topicIds. |
Broadcasts
| Code | Status | Meaning |
|---|---|---|
INVALID_SCHEDULE | 400 | scheduledAt is not a future date. |
NO_RECIPIENTS | 400 | Nobody in the audience can be sent to. The message lists a few skipped recipients and why. |
BROADCASTS_NOT_ALLOWED | 402 | The plan does not include broadcasts. |
BROADCAST_NOT_FOUND | 404 | No such broadcast. |
BROADCAST_LOCKED | 409 | The broadcast's status does not allow this action, or a send is already starting. |
BROADCAST_SENDING | 409 | Cannot delete while sending. Cancel first. |
SEND_UNAVAILABLE | 503 | Sending cannot start right now. Retry shortly. |
Sending a broadcast can also return the sending, domain, quota and reputation codes above.
Logs, suppressions and jobs
| Code | Status | Meaning |
|---|---|---|
EMAIL_NOT_FOUND | 404 | No such email. |
SUPPRESSION_NOT_FOUND | 404 | No such suppression entry. |
JOB_NOT_FOUND | 404 | No such job. |
INVALID_CURSOR | 400 | cursor is not a value returned by the previous page. |
Webhooks
| Code | Status | Meaning |
|---|---|---|
INVALID_URL | 400 | Not an https:// URL with a public, fully qualified hostname, or it contains credentials. |
NO_EVENTS | 400 | No valid event name in events. |
WEBHOOK_LIMIT_REACHED | 402 | The plan allows no more webhooks. |
WEBHOOK_NOT_FOUND | 404 | No such webhook. |
Inbox
| Code | Status | Meaning |
|---|---|---|
PLAN_REQUIRED | 402 | The plan does not include receiving. |
INVALID_HOST | 400 | The receiving host is not the domain or one of its subdomains, or it is the MAIL FROM host. |
DOMAIN_NOT_VERIFIED | 409 | Verify the domain before enabling receiving. |
INBOUND_HOST_TAKEN | 409 | The host already receives mail in another project. |
DOMAIN_NOT_RECEIVING | 409 | Enable receiving on the domain before creating addresses. |
INVALID_ADDRESS | 400 | Invalid local part or forwarding address. |
ADDRESS_EXISTS | 409 | The address already exists. |
ADDRESS_NOT_FOUND | 404 | No such address. |
MAILBOX_NOT_FOUND | 404 | The mailbox for an alias was not found. |
MAILBOX_LIMIT | 402 | The plan allows no more mailboxes. |
ALIAS_LIMIT | 402 | The mailbox has the maximum number of aliases. |
FORWARDING_NOT_ALLOWED | 402 | The plan does not include forwarding. |
ALIAS_SETTINGS | 400 | Mailbox-only settings (name, forwarding, notifications, password) were set on an alias. |
WEAK_PASSWORD | 400 | The webmail password is shorter than 10 characters. |
MESSAGE_NOT_FOUND | 404 | No such message. |
ATTACHMENT_NOT_FOUND | 404 | No attachment with that index. |
RAW_NOT_AVAILABLE | 404 | The original message was not kept. |
INVALID_FOLDER | 400 | Unknown folder, or a draft moved anywhere but trash. |
NOTHING_TO_UPDATE | 400 | None of isRead, isStarred, folder. |
TOO_MANY_IDS | 400 | More than 500 ids in a bulk update. |
BODY_REQUIRED | 400 | A reply with neither text nor html. |
INVALID_CURSOR | 400 | cursor is not a value returned by the previous page. |