All errors from the Maildeno SDKs and REST API use a consistent code field for programmatic handling.

Complete error code table

Code HTTP status Cause Resolution

INVALID_API_KEY

401

The key is missing, malformed, revoked, or expired.

Generate a new key at Dashboard → API Keys. Check that you are reading the key from the correct environment variable.

FORBIDDEN

403

The key’s target scope does not include the requested render target. For example, a key scoped to ["html"] was used to request "mjml".

Create a new key with the required targets, or change the target in your request to one the key allows.

TEMPLATE_NOT_FOUND

404

The template_id UUID does not match any template in your account.

Verify the template ID in the dashboard. Check that the template has not been deleted.

RENDER_ERROR

422

The request body is structurally invalid (e.g. template_id is not a UUID) or the render pipeline encountered an unrecoverable error.

Inspect err.issues for field-level validation details. Confirm the template ID is a valid UUID v4.

NETWORK_ERROR

0

The underlying HTTP transport could not reach the API. Causes: no internet, DNS failure, firewall, connection refused.

Check network connectivity. Implementing retry-with-backoff is recommended for transient failures.

TIMEOUT

0

The request exceeded the configured timeout (default: 30 s for JS, 30.0 s for Python).

Increase timeout in the client constructor. If timeouts are frequent, check for unusually large templates or network latency.

Checking err.issues (validation errors)

When code is RENDER_ERROR and the cause is invalid input, the issues array provides field-level detail:

  • JavaScript

  • Python

try {
  await client.renderHtml("not-a-uuid")
} catch (err) {
  if (err instanceof MaildenoError && err.issues) {
    for (const issue of err.issues) {
      console.error(issue.loc.join("."), "—", issue.msg)
    }
  }
}
// body.template_id — Input should be a valid UUID, ...
try:
    client.render_html("not-a-uuid")
except MaildenoError as err:
    if err.issues:
        for issue in err.issues:
            loc = ".".join(map(str, issue["loc"]))
            print(f"{loc} — {issue['msg']}")
# body.template_id — Input should be a valid UUID, ...

HTTP status to error code mapping

401  →  INVALID_API_KEY
403  →  FORBIDDEN
404  →  TEMPLATE_NOT_FOUND
422  →  RENDER_ERROR   (+ err.issues for validation failures)
0    →  NETWORK_ERROR  or  TIMEOUT  (transport-level)

All other 4xx / 5xx responses will surface as RENDER_ERROR with the raw API message in err.message.