Errors
Drin uses conventional HTTP status codes and returns a consistent JSON envelope on any non-2xx response, so you can branch on a stable machine-readable type.
{
"error": {
"type": "validation_error",
"message": "`to` must contain at least one recipient.",
"param": "to"
}
}| Field | Description |
|---|---|
type | Machine-readable category — switch on this (see table below). |
message | Human-readable explanation. Safe to log; don't parse it. |
code | Optional finer-grained code, when present. |
param | Optional offending request field, when the error is about input. |
01 Error types
type | Status | Meaning |
|---|---|---|
validation_error | 400 / 422 | The request was malformed or failed validation (param points at the field). |
authentication_error | 401 | Missing, malformed, or revoked API key. |
permission_error | 403 | The key is valid but not allowed to do this — wrong project, sandbox restriction, or plan limit. |
not_found | 404 | No such resource for this account. |
conflict | 409 | Conflicts with current state — e.g. an idempotency replay with a different body, or deleting a domain with sending history. |
suppressed | 409 | Every recipient is on this project's suppression list, so nothing was sent. |
rate_limited | 429 | Too many requests — back off and retry (see below). |
internal_error | 5xx | Something went wrong on our side. Safe to retry idempotently. |
02 Request IDs
Every response echoes a request id in the X-Request-Id header. Include it when you contact support — it lets us find the exact request in our logs.
03 Rate limits
When you exceed a limit you get a 429 with a Retry-After header (seconds). Wait that long, then retry. The SDK reads Retry-After and backs off automatically.
04 Handling errors in the SDK
Each type maps to a typed error subclass, so you can branch with instanceof or on err.type:
import {
DrinError,
DrinValidationError,
DrinRateLimitError,
DrinSuppressedError,
} from "@drin00/sdk";
try {
await drin.emails.send({ /* … */ });
} catch (err) {
if (err instanceof DrinSuppressedError) {
// every recipient is suppressed — nothing was sent
} else if (err instanceof DrinRateLimitError) {
await sleep((err.retryAfter ?? 1) * 1000);
} else if (err instanceof DrinValidationError) {
console.error("Bad field:", err.param, err.message);
} else if (err instanceof DrinError) {
console.error(err.type, err.status, err.requestId);
}
}Automatic retriesThe SDK auto-retries
429 and 5xx responses with exponential backoff and full jitter (default: 2 retries). A POST is only retried when you pass an Idempotency-Key, so sends are never duplicated.