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 |
|---|---|---|---|
|
|
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. |
|
|
The key’s target scope does not include the requested render target. For example, a key scoped to |
Create a new key with the required targets, or change the |
|
|
The |
Verify the template ID in the dashboard. Check that the template has not been deleted. |
|
|
The request body is structurally invalid (e.g. |
Inspect |
|
|
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. |
|
|
The request exceeded the configured timeout (default: 30 s for JS, 30.0 s for Python). |
Increase |
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.