Skip to content

Errors

PostStack uses standard HTTP status codes and returns JSON error responses. All error responses follow the same format.

Error Response Format

All error responses include an error field with a human-readable message and a stable, machine-readable code you can branch on (the wording may change; the code will not):

json
{
  "error": "Domain not found",
  "code": "not_found"
}

The SDK throws typed errors that you can catch and inspect:

typescript
import { PostStackError } from '@poststack.dev/sdk';

try {
  await poststack.emails.send({ ... });
} catch (err) {
  if (err instanceof PostStackError) {
    // err.status  = 422
    // err.code    = "unprocessable_entity"
    // err.message = "Domain not verified"
  }
}

HTTP Status Codes

StatusMeaningDescriptionCode
200OKRequest succeeded. Response body contains the result.
201CreatedResource created successfully.
202AcceptedRequest accepted and queued asynchronously. Returned by POST /emails and POST /emails/batch — the body contains the new id(s) but delivery happens out-of-band.
400Bad RequestThe request body is malformed or missing required fields.invalid_request
401UnauthorizedMissing or invalid API key.unauthorized
403ForbiddenAPI key does not have permission for this action.forbidden
404Not FoundThe requested resource does not exist.not_found
409ConflictThe resource already exists (e.g., duplicate domain or contact email).conflict
422Unprocessable EntityValidation failed. The request body contains invalid data.unprocessable_entity
429Too Many RequestsRate limit exceeded. Check the Retry-After header.rate_limit_exceeded
500Internal Server ErrorSomething went wrong on our end. Try again later.internal_error

Common Errors

Here are common error messages and how to resolve them:

ErrorStatusResolution
Invalid API key401Check that your API key is correct and starts with sk_live_ or sk_test_.
API key does not have permission403Use a key with full_access permission, or upgrade the key permissions.
Domain not verified422Verify your domain DNS records before sending. Use POST /domains/:id/verify.
Domain not found404Add the domain first with POST /domains.
Contact already exists409A contact with this email already exists. Use PATCH to update instead.
Rate limit exceeded429Wait and retry after the duration specified in the Retry-After header.
Batch size exceeds limit422Reduce batch to 100 emails or fewer per request.
Template not published422Publish the template with POST /templates/:id/publish before using it.
Missing required field: to400Include the "to" field as an array of email addresses.
Invalid email address422Check the email address format in the "from" or "to" fields.

Rate Limits

Every API response carries three headers describing your current bucket so you can back off proactively instead of waiting for a 429:

HeaderMeaning
X-RateLimit-LimitMaximum requests allowed in the current window.
X-RateLimit-RemainingRequests still available before throttling kicks in.
X-RateLimit-ResetUnix epoch seconds when the current window resets.
Retry-AfterOnly set on 429 responses — seconds to wait before retrying.

When you exceed the limit the API returns a 429 with the same X-RateLimit-* headers plus Retry-After:

bash
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1748430120
Retry-After: 47
Content-Type: application/json

{
  "error": "Rate limit exceeded. Please try again later.",
  "code": "rate_limit_exceeded"
}

The SDK automatically handles rate limiting with exponential backoff and retries.