Skip to content

Errors

Every error Chaski throws is a ChaskiError with a code, and the same codes answer from its routes.

ChaskiError

Chaski throws ChaskiError, with a code from a short, fixed list and a message that says what to change. Its routes answer with the same code, as JSON:

JSON
{ "error": "validation", "message": "scheduledAt is in the past" }
CodeHTTP statusWhen
validation400The request or a template is wrong: a missing field, an unknown type, Liquid that won’t parse.
config500The Chaski Worker’s setup is: a missing binding, a channel setting it can’t use, migrations not applied.
unauthorized401No one is signed in.
forbidden403The signed-in user may not do this, or the request came from an origin Chaski doesn’t know.
not_found404The template, delivery or endpoint doesn’t exist.
conflict409It already exists, or another request holds it.
delivery_failed502An inline send’s email didn’t go out; the message carries the provider’s error.
channel_paused503An inline send’s email is held: someone paused the email channel.
internal500Something failed along the way that a retry may fix.

Check for one

Detect a Chaski error with isChaskiError(), not instanceof: the error keeps its name and code when it crosses a service binding, but not its class. Between two local wrangler dev sessions it keeps neither; see Connect your app.

TypeScript
import { isChaskiError } from 'chaskijs';

try {
  await env.CHASKI.send(request);
} catch (error) {
  if (isChaskiError(error) && error.code === 'validation') {
    // the request needs fixing; trying again won't help
  }
  throw error;
}

Retries

isRetryableChaskiError() is the rule Chaski’s own queue consumer follows, for a job of your app that calls Chaski: internal errors are worth another try, every other code is permanent, and errors that aren’t Chaski’s, such as a network failure, are assumed to pass. delivery_failed and channel_paused count as permanent: they answer someone waiting on the email, and they decide whether to ask again.

TypeScript
import { isRetryableChaskiError } from 'chaskijs';

if (isRetryableChaskiError(error)) message.retry();

In the browser

chaskijs/client throws ChaskiRequestError, a ChaskiError that also carries the HTTP status the route answered with. An answer without a code Chaski knows arrives as internal.

TypeScript
import { ChaskiRequestError } from 'chaskijs/client';

try {
  await client.preferences.update({ mutedTypes });
} catch (error) {
  if (error instanceof ChaskiRequestError && error.status === 401) {
    // the session ended: send the user to sign in
  }
}

Updated Oct 30, 2026

MIT License · chaski.sh