Errors & Rate Limits
The Emalc Public Email API uses standard HTTP response codes to indicate the success or failure of API requests.
Standard Error Response Format#
All 4xx and 5xx error responses return a standardized JSON body:
json
{
"error": "ERROR_CODE",
"message": "Human readable summary of why the request failed.",
"issues": []
}error(string): Machine-readable uppercase error string.message(string): Detailed explanation of the error.issues(array, optional): Populated whenerrorisVALIDATION_ERROR(400), containing Zod path-level schema validation errors.
HTTP Status & Error Code Matrix#
| Status | Error Code | Description & Required Remediation |
|---|---|---|
400 | VALIDATION_ERROR | Request body failed schema validation. Check issues array for invalid fields or types. |
400 | BAD_REQUEST | Malformed JSON body, invalid sender email, or non-UUID path parameter. |
401 | UNAUTHORIZED | API key is missing, malformed, or inactive. Verify Authorization: Bearer em_* header. |
402 | INSUFFICIENT_CREDITS | Neither subscription quota nor wallet balance has credits available. Top up in dashboard. |
403 | FORBIDDEN | Key lacks required scope (email:send or email:read). |
403 | DOMAIN_NOT_ALLOWED | Sender domain is prohibited by API key's domain_scope setting. |
403 | DOMAIN_NOT_FOUND | Sender domain is not registered in your organization. |
403 | DOMAIN_NOT_VERIFIED | Sender domain DNS / DKIM verification is incomplete. |
404 | NOT_FOUND | The requested emailId does not exist or belongs to another organization. |
422 | ALL_RECIPIENTS_SUPPRESSED | All recipient emails in to are on your organization's suppression list. |
429 | RATE_LIMIT_EXCEEDED | Exceeded per-second (2 req/s) or daily (5,000 req/day) limit. Read Retry-After header. |
500 | SEND_FAILED | Internal delivery failure. Credits for failed sends are automatically refunded. |
Rate Limits & Capacity#
Rate limits are enforced per organization:
- Per-Second Limit: 2 HTTP requests / second
- Daily Request Limit: 5,000 HTTP requests / day (Resets daily at
00:00:00 UTC). - Total Daily Delivery Capacity: 5,000 emails / day per organization.
Recipients Batching Note: Sending to up to 10 recipients in a single
POST /api/public/email/sendcall (e.g.to: ["a@acme.com", "b@acme.com", ...]) counts as 1 API request toward your 5,000 daily limit.
Rate Limit Response Headers#
Every API response includes rate limit telemetry headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum allowed requests in current window (2 or 5000). |
X-RateLimit-Remaining | Remaining requests available in current window. |
X-RateLimit-Reset | Seconds remaining until the current window resets. |
Retry-After | Sent on 429 RATE_LIMIT_EXCEEDED. Seconds to pause before retrying. |
Handling 429 Rate Limit Exceeded#
When receiving a 429 response, inspect the Retry-After header and execute an exponential backoff retry pattern:
typescript
if (response.status === 429) {
const retryAfterSeconds = parseInt(
response.headers.get('Retry-After') || '1',
10
);
await new Promise((resolve) => setTimeout(resolve, retryAfterSeconds * 1000));
return retryRequest();
}