Misar Docs
MisarMailMisar.BlogMisarReachMisarPostMisar.DevMisar PlatformMisar IdentityMisar Posts API
Api Reference

Error Codes

HTTP status codes and error responses from the MisarMail API

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

StatusMeaningCommon Causes
200OKRequest succeeded; email delivered
202QueuedPrimary SMTP unavailable; email auto-retried
400Bad RequestInvalid request body or failed validation
401UnauthorizedAPI key missing, invalid, or revoked
403Forbiddenfrom.email not in your verified accounts, or key lacks required scope
404Not FoundEndpoint doesn't exist
413Request Too LargeRequest body exceeds maximum size
415Unsupported Media TypeContent-Type must be application/json
422SuppressedSend suppressed — recipient unsubscribed or bounced
429Too Many RequestsRate limit or plan limit reached
500Server ErrorUnexpected 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.misar.io/mail/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");
}