Docs
guides · errors

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 envelope
{
  "error": {
    "type": "validation_error",
    "message": "`to` must contain at least one recipient.",
    "param": "to"
  }
}
FieldDescription
typeMachine-readable category — switch on this (see table below).
messageHuman-readable explanation. Safe to log; don't parse it.
codeOptional finer-grained code, when present.
paramOptional offending request field, when the error is about input.

01 Error types

typeStatusMeaning
validation_error400 / 422The request was malformed or failed validation (param points at the field).
authentication_error401Missing, malformed, or revoked API key.
permission_error403The key is valid but not allowed to do this — wrong project, sandbox restriction, or plan limit.
not_found404No such resource for this account.
conflict409Conflicts with current state — e.g. an idempotency replay with a different body, or deleting a domain with sending history.
suppressed409Every recipient is on this project's suppression list, so nothing was sent.
rate_limited429Too many requests — back off and retry (see below).
internal_error5xxSomething 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:

Node.js
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.