Error Codes
HTTP status codes and error responses from the MisarMail API
Error Codes
All errors return a JSON body with a success: false field and an error string.
{
"success": false,
"error": "Human-readable error message"
}Validation errors (400) include a details object in Zod flatten format:
{
"success": false,
"error": "Validation failed",
"details": {
"fieldErrors": {
"to": ["Required"]
},
"formErrors": []
}
}details.fieldErrors is a map of field names to arrays of error strings. details.formErrors contains top-level (non-field) validation errors.
Status Code Reference
| Status | Meaning | Common Causes |
|---|---|---|
200 | OK | Request succeeded; email delivered |
202 | Queued | Primary SMTP unavailable; email auto-retried |
400 | Bad Request | Invalid request body or failed validation |
401 | Unauthorized | API key missing, invalid, or revoked |
403 | Forbidden | from.email not in your verified accounts, or key lacks required scope |
404 | Not Found | Endpoint doesn't exist |
413 | Request Too Large | Request body exceeds maximum size |
415 | Unsupported Media Type | Content-Type must be application/json |
422 | Suppressed | Send suppressed — recipient unsubscribed or bounced |
429 | Too Many Requests | Rate limit or plan limit reached |
500 | Server Error | Unexpected platform error |
Error Details
401 — Invalid API Key
{
"success": false,
"error": "Invalid or missing API key. Use: Authorization: Bearer msk_..."
}Fix: Check that you're sending the correct msk_... key in the Authorization: Bearer header.
403 — Wrong Sender
{
"success": false,
"error": "'from.email' is not a verified account for this API key"
}Fix: The from.email must exactly match one of the email accounts owned by the API key's user. Create or verify the address in Settings → Email Accounts.
403 — Missing Scope
{
"success": false,
"error": "API key does not have 'send' scope"
}Fix: Revoke the key and create a new one with the required scope checked.
400 — Validation Failed
{
"success": false,
"error": "Validation failed",
"details": {
"fieldErrors": {
"subject": ["Required"],
"to.0.email": ["Invalid email address"]
},
"formErrors": []
}
}Fix: Check details.fieldErrors for per-field error messages.
400 — Inactive SMTP Pool
{
"success": false,
"error": "alias_id not found or pool inactive"
}Fix: The specified alias_id doesn't exist or the SMTP pool is currently paused. Omit alias_id to use automatic pool selection.
413 — Request Too Large
{
"success": false,
"error": "Request body exceeds maximum size"
}Fix: Reduce the size of your request body. Large HTML bodies or base64-encoded attachments are common causes.
415 — Unsupported Media Type
{
"success": false,
"error": "Content-Type must be application/json"
}Fix: Set the Content-Type: application/json header on your request.
422 — Suppressed
{
"success": false,
"suppressed": true,
"error": "Send suppressed — recipient unsubscribed or bounced"
}The suppressed: true flag indicates the send was intentionally blocked because the recipient is on your suppression list (previously unsubscribed or hard-bounced). This protects your sender reputation and deliverability. Do not retry — check the recipient's status via the Contacts API and remove them from future send lists.
429 — Plan Limit Reached
{
"success": false,
"error": "Daily send limit reached. Upgrade at mail.misar.io/pricing"
}Or monthly:
{
"success": false,
"error": "Monthly send limit reached. Upgrade at mail.misar.io/pricing"
}Fix: Upgrade your plan at mail.misar.io/pricing.
202 — Queued (Not an Error)
{
"success": false,
"queued": true,
"message": "Email queued for retry"
}Despite success: false, 202 is not an error. The email will be delivered automatically when the SMTP provider recovers. Do not retry on 202.
Retry Strategy
Do retry
Network errors, 5xx responses, connection timeouts
Do NOT retry
4xx errors (fix the request first), 202 Queued (already handled), 422 Suppressed (intentional block)
Recommended backoff for retryable errors:
async function sendWithRetry(payload: EmailPayload, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch("https://api.mail.misar.io/v1/send", {
method: "POST",
headers: { "Authorization": `Bearer ${process.env.MISARMAIL_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await res.json();
// 202 = queued — success, stop retrying
if (res.status === 202) return data;
// 422 = suppressed — intentional, stop retrying
if (res.status === 422 && data.suppressed) return data;
// 2xx = success
if (res.ok) return data;
// 4xx = client error — do not retry
if (res.status >= 400 && res.status < 500) {
throw new Error(data.error || `HTTP ${res.status}`);
}
// 5xx — retry with exponential backoff
if (attempt < maxRetries - 1) {
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
}
}
throw new Error("Max retries exceeded");
}