Error codes & fixes
Every error code you can meet, what it means, whether it is safe to retry, and the exact action that resolves it.
Every refused action in DineGlobe answers with the same small, predictable envelope: a machine-readable code, a requestId you can quote, and an explicit statement of whether sending the same request again is safe. This page is the complete lookup of every code, grouped by area, followed by the state machines that explain why a record refuses an action. If you are starting from a symptom instead of a code, use Troubleshooting by scenario instead.
How to read an error
Every error — whether it reaches a guest screen, a staff panel or an API client — carries only these fields:
| Field | What it holds |
|---|---|
code |
The safe identifier from the tables below (for example SLOT_UNAVAILABLE). The human-readable message equals the code, so nothing internal can leak through a message. |
requestId |
An id for this exact attempt. Quote it when asking staff, the operator or support to investigate. |
fieldErrors |
Optional per-field messages, present when the problem is tied to specific inputs (for example paymentMode on PAYMENT_NOT_READY). |
retryable |
true only when repeating the same request is safe and expected to succeed. Only the rows marked Yes below set it. |
What never appears in an error: stack traces, SQL, provider responses, or any other tenant’s data.
Two denial answers are deliberately different:
- Not found (
NOT_FOUND, 404) means “does not exist, or not visible in your scope”. A venue, booking or export that belongs to someone else looks exactly like one that does not exist at all. The system never reveals whether an id exists outside your reach. - Permission denied (
PERMISSION_DENIED, 403) means you are signed in, the venue is in your scope, and your role does not include this action. Ask an owner or org admin for the role, or have them perform the action.
Guests and staff see the same envelope rendered differently:
- Guest screens (the public booking flow and the
/bportal) render codes as plain sentences:RATE_LIMITEDbecomes “Too many attempts — please wait a moment.”,RESERVATION_STALEbecomes “This booking changed — reload the page and try again.”, a lost slot becomes “That time just went to another guest — the nearest open times are attached.” - Staff console screens show the message in an alert region (for example “Stale form — reload the page.” on a version clash, or “The venue is busy — retry the same request.” for
VENUE_LOCK_TIMEOUT). The operator panels under/app/panelsadditionally show the raw code and the per-field errors. - API and webhook surfaces answer with the raw JSON envelope.
One special case sits outside this taxonomy: on a device with no connection, every non-reading request is refused with
503and the codeOFFLINE. Nothing was recorded and nothing is queued — DineGlobe never stores bookings or arrivals on a device. Reconnect and repeat the action; the offline screen explains the paper fallback.
Generic codes
| Code | Status | Safe to retry? | When you see it | What to do |
|---|---|---|---|---|
VALIDATION |
400 | No | A field was malformed, missing or out of range, or a business precondition failed with field detail. | Fix the fields named in fieldErrors and submit again. |
UNAUTHENTICATED |
401 | No | Not signed in, the session expired or was revoked, or the email/password pair was wrong. Wrong-password denials are deliberately generic. | Sign in again at /app/login; use password recovery if needed. |
TOKEN_INVALID |
401 | No | A one-time link or secret is unknown, expired, revoked or already used. Every token class answers identically: guest booking links, staff invitations, waitlist queue tokens, check-in passes, password-reset links. | Request a fresh link, invitation or pass. If it was already used, the original action succeeded — check the booking, the queue board or the reservation. |
PERMISSION_DENIED |
403 | No | Authenticated and in scope, but your role lacks the permission. | Ask an OWNER or ORG_ADMIN to grant the role or to perform the action. |
NOT_FOUND |
404 | No | The resource does not exist or is not visible in your scope. Cross-tenant probing is answered identically. | Check the id or link; if you expected access, confirm your membership covers that venue. |
CONFLICT |
409 | No | A version or constraint clash: an overlapping table allocation, retrying a message job that is not FAILED, or a constraint backstop. |
Reload the record and retry; for a genuine clash pick a different slot or name. |
RULE_VIOLATION |
422 | No | Well-formed input that breaks a business rule: joining a queue that is not running, arrivals above party size, marking a no-show before the grace window elapses, requesting publication while already PUBLISHED. | The action becomes possible when the stated condition changes; correct the value or wait. |
RATE_LIMITED |
429 | Yes | Too many attempts in a window: password sign-in, guest link recovery, demo sign-ins, portal exchanges. The response may carry a retry delay. | Wait the indicated delay (or a few minutes) and try again. |
DEPENDENCY_UNAVAILABLE |
503 | Yes | A downstream dependency is not answering. | Retry shortly; if it persists, contact the operator. |
INTERNAL_ERROR |
500 | No | An unexpected failure; the response carries only a request ID. | Retry once; if it repeats, report the requestId. |
OFFLINE |
503 | Yes, once back online | The device has no connection. Non-reading requests are refused; nothing was recorded and nothing is waiting to be sent. | Reconnect and repeat the action; take paper notes at the door in the meantime. |
Reservations & booking
| Code | Status | Safe to retry? | When you see it | What to do |
|---|---|---|---|---|
SLOT_UNAVAILABLE |
409 | No | Another party took the tables between your availability read and the booking commit. The response carries up to 4 nearest open alternative slots. | Pick one of the attached alternatives and rebook — no need to hunt manually. |
AVAILABILITY_STALE |
409 | No | The venue’s schedule or offerings changed after your availability snapshot. | Reload availability for the same day and book from the fresh picture. |
POLICY_STALE |
409 | No | The booking terms changed between accepting the policy and committing the booking. | Review and accept the current policy, then rebook. |
IDEMPOTENCY_MISMATCH |
409 | No | The same request key was replayed with a different payload. Request keys live 24 hours. | Do not reuse the key. Start the changed booking with a fresh request key. |
PAID_BOOKING_UNAVAILABLE |
422 | No | The offering charges a deposit but payments are not available right now — no charge-capable account, or a deposit-plus-request combination that V1 does not offer. | Book a free or request offering if one exists, or ask the venue to fix its payment account. Paid mode is never pretended. |
RESERVATION_STALE |
409 | No | The reservation changed since you read it — a version mismatch on decide, cancel, no-show or reschedule. | Reload the reservation and redo the action against the current version. |
RESERVATION_STATE_CONFLICT |
409 | No | The reservation is not in REQUESTED state, so it cannot be decided or expired. It was already approved, declined or expired. | Reload to see the outcome — someone decided it first. |
RESERVATION_INVALID_TRANSITION |
422 | No | The action needs an editable record. Only CONFIRMED bookings or REQUESTED requests can be rescheduled, cancelled or marked no-show. | Expired, declined and cancelled records cannot be edited — create a new booking instead. |
REBOOK_REQUIRED |
409 | No | A self-service edit would change the booked amount, currency or terms. | Cancel and rebook under the current disclosed terms; the edit path never charges anything. |
Inventory, layout & scheduling
| Code | Status | Safe to retry? | When you see it | What to do |
|---|---|---|---|---|
SLUG_TAKEN |
409 | No | A venue create or rename asked for a public address already used by a current or historical venue — retired addresses are never handed out again. The field error names a free -2-style alternative. |
Use the suggested alternative address, or pick a different one. |
AREA_NAME_TAKEN |
409 | No | Another area of the same venue already uses that name. | Choose a different area name; names are unique per venue. |
RESOURCE_LABEL_TAKEN |
409 | No | Another resource of the same venue already uses that label. | Choose a different label. |
COMBINATION_NAME_TAKEN |
409 | No | Another table combination of the same venue already uses that name. | Rename the combination. |
LAYOUT_STALE |
409 | No | The floor layout changed since your editor loaded it. | Refresh the layout editor and re-apply the change. |
OFFERING_NAME_TAKEN |
409 | No | Another offering of the same venue already uses that name. | Use a distinct offering name. |
EXCEPTION_DATE_TAKEN |
409 | No | A schedule exception already covers that business date. | Edit the existing exception for that date instead of adding a second one. |
VENUE_LOCK_TIMEOUT |
503 | Yes | The per-venue inventory lock timed out under heavy concurrent writing on the same venue. | Retry the same action — it is safe and expected to succeed on retry. |
Identity, MFA & tenancy
| Code | Status | Safe to retry? | When you see it | What to do |
|---|---|---|---|---|
EMAIL_TAKEN |
409 | No | Registration used an email that already has an account. | Sign in instead, or use password recovery. |
IDENTITY_LINK_REQUIRED |
409 | No | A provider sign-in maps to an existing password account that must be linked first. | Sign in with the password once, then connect the provider from account settings. |
MFA_REQUIRED |
401 | No | The action needs a verified authenticator code: sign-in for MFA-enrolled staff, money actions with a recency window, platform and operations surfaces. | Complete the code prompt, then retry the action promptly — the assurance is time-boxed. |
MFA_CODE_INVALID |
401 | No | The 6-digit code was wrong, reused, or outside its time step. Each code works once. | Enter the current code from your authenticator, or use one of your one-time recovery codes. |
MFA_ALREADY_CONFIRMED |
409 | No | Confirming an enrollment that is already confirmed. | Nothing to do — two-step verification is already active. |
LAST_OWNER |
422 | No | Removing the last OWNER of an organization or venue. | Transfer ownership to another member first. |
TRANSFER_TARGET_NOT_MEMBER |
422 | No | An ownership transfer targeted someone who is not a member. | Invite the person as a member, then transfer. |
Check-in & visits
| Code | Status | Safe to retry? | When you see it | What to do |
|---|---|---|---|---|
CHECKIN_PASS_WRONG_VENUE |
422 | No | A pass was scanned at a different venue than the one that issued it. Nothing about the other booking is revealed. | Direct the guest to the issuing venue. |
CHECKIN_PASS_WINDOW |
422 | No | The scan happened outside the pass window — earlier than 60 minutes before the booked start or after the booked end. | Scan within the service window: booked start minus 60 minutes through the booked end. |
VISIT_STALE |
409 | No | Another actor moved the visit first — a version mismatch. | Reload the visit and redo the action. |
VISIT_INVALID_TRANSITION |
422 | No | The visit state forbids the action: seating without an arrival, seating twice, recording an arrival after departure. | Check the visit board state; seat only after at least one arrival, and never after DEPARTED. |
VISIT_EVENT_MISMATCH |
409 | No | The request key recorded a different intent for this visit earlier. | Use a new request key for the new action; the original already stands. |
OCCUPANCY_CONFLICT |
409 | No | The table is still physically occupied by another party. The 409 names who holds it. | Seat the party at a free table, or complete-and-clear the overstaying visit first — no timer will free the table for you. |
Waitlist
| Code | Status | Safe to retry? | When you see it | What to do |
|---|---|---|---|---|
WAITLIST_DUPLICATE |
409 | No | This email or phone already holds an active (WAITING or OFFERED) entry for the venue today. | Wait for the existing entry to be called; use leave-queue first if you want to rejoin. |
WAITLIST_STALE |
409 | No | The queue entry changed since your read — a version mismatch on offer, cancel or leave. | Reload the queue board and retry. |
WAITLIST_OFFER_CONFLICT |
409 | No | The offer is no longer OPEN: already accepted, host-cancelled, or the entry is not WAITING. | Reload: if it converted, the booking exists; if it was cancelled, the entry is back in the queue at its original place. |
WAITLIST_OFFER_EXPIRED |
422 | No | The 5-minute offer window closed before acceptance. | Nothing was lost — the entry returned to WAITING keeping its original place. Watch for the next offer and accept within 5 minutes. |
Payments & refunds
Payments and SMS run behind explicit capability gates. During the unpaid pilot the gates are closed: no production provider is configured, so payment actions answer with honest refusals rather than pretend success. The published scope statement reads: single-market pilot evidence only — one approved country and its listed capabilities do not extend further; unlisted countries and capabilities stay disabled (rendered verbatim on the public /capabilities page).
| Code | Status | Safe to retry? | When you see it | What to do |
|---|---|---|---|---|
PAYMENT_PROVIDER_UNAVAILABLE |
503 | Yes | No payment provider is configured for the deployment. | Connect a provider first; retry after configuration. |
PAYMENT_COUNTRY_NOT_APPROVED |
422 | No | The venue’s country has no approved payment capability. | Payments stay unavailable for that country until the operator approves it. |
PAYMENT_ROUTE_NOT_APPROVED |
422 | No | The approved capability names a different provider than the one in use. | The operator must align the capability’s provider with the connected account. |
PAYMENT_NOT_READY |
422 | No | A DEPOSIT offering cannot be activated or checked out because the venue’s connected account is not charge-ready. The paymentMode field error names the block: NO_ACCOUNT, CHARGES_DISABLED, CURRENCY_MISMATCH or COUNTRY_NOT_APPROVED. |
Fix the named account problem — connect the account, refresh capability truth, match the currency, or await country approval — then retry. |
PAYMENT_CHECKOUT_TIMEOUT |
503 | Yes | The provider did not answer during checkout; the hosted session may exist. | Retry the same action — the stable idempotency key continues the SAME checkout and can never create a second charge. |
PAYMENT_WEBHOOK_REJECTED |
400 | No | A provider callback failed its raw-body signature check or named an unknown account. Nothing was stored. | Nothing for a user to do; the sender must not re-send the same callback. Staff should check provider configuration. |
PAYMENT_REFUND_TIMEOUT |
503 | Yes | The provider refund call timed out after the refund may have been stored. | Retry — the next sweep recovers the SAME refund via its intent id, never a second one. |
PAYMENT_REFUND_REJECTED |
422 | No | The provider permanently refused the refund. This is terminal. | Resolve manually with the provider; the system never double-collects to work around it. |
REFUND_EXCEEDS_AVAILABLE |
422 | No | The refund would exceed the refundable balance: captured net minus settled refunds, open disputes and pending intents. | Refund at most the stated available amount in minor units. |
REFUND_INVALID_TRANSITION |
422 | No | The refund intent is not in a state that accepts approve, execute or cancel. | Reload the intent — it may already be settled, failed or cancelled. |
PAYMENT_RECONCILIATION_INVALID |
500 | No | A paid HELD reservation lacked its accepted policy snapshot at confirmation; the system refused to confirm without its terms. | Internal integrity stop — contact support with the requestId. |
SaaS billing
| Code | Status | Safe to retry? | When you see it | What to do |
|---|---|---|---|---|
BILLING_WEBHOOK_REJECTED |
400 | No | A billing callback failed signature verification or references an unknown subscription. | The operator checks provider configuration; the sender must not replay the same callback. |
BILLING_EVENT_UNROUTABLE |
422 | No | A stored event’s routing target (its subscription) no longer resolves. | The operator reconciles the billing account; the event stays evidenced. |
BILLING_PRICE_MISMATCH |
422 | No | The event’s price id is not the V1 plan price. | The operator verifies the provider price configuration. |
SUBSCRIPTION_EXISTS |
409 | No | The venue already holds a live subscription. | Manage the existing subscription instead of starting another. |
SUBSCRIPTION_INTAKE_SUSPENDED |
402 | No | NEW booking intake is blocked: the trial ended, or a past-due grace period expired, or the period ended. Existing bookings, cancellation, refunds and exports still work. | Settle the subscription (pay or renew); new bookings resume automatically once entitlement is restored. |
Import & export
| Code | Status | Safe to retry? | When you see it | What to do |
|---|---|---|---|---|
IMPORT_CSV_MALFORMED |
400 | No | The CSV structure is broken — unterminated quotes or a quote inside an unquoted cell. | Fix the CSV quoting and upload again. |
IMPORT_CSV_EMPTY |
400 | No | The file has no header row. | Upload a non-empty CSV that starts with a header row. |
IMPORT_ENCODING |
400 | No | The file is not valid UTF-8. This is refused before any row is stored. | Re-export the file as UTF-8, then upload again. |
IMPORT_TOO_LARGE |
400 | No | The file exceeds 10 MB. | Split the file. |
IMPORT_HEADER_MISSING |
400 | No | A mapped field names a column the file does not have. Each missing column is named in fieldErrors. |
Correct the column mapping to the file’s actual headers. |
IMPORT_TOO_MANY_ROWS |
400 | No | The file has more than 10,000 data rows. | Split into batches of at most 10,000 rows. |
IMPORT_OFF_GRID |
422 | No | A row’s start time does not sit on any open slot of the current schedule. | Align imported times to the venue’s bookable slots — check schedules and date exceptions — then import again. |
IMPORT_PAID_POLICY |
422 | No | The governing policy for a row is a paid (DEPOSIT) policy. Imported bookings are never charged, so the row is refused. | Import only non-paid offerings; handle paid bookings through the normal booking flow. |
EXPORT_NOT_READY |
409 | Yes | The export artifact is still rendering (job QUEUED) or the file is not attached yet. | Wait a moment and retry the download. |
EXPORT_FAILED |
410 | No | The render permanently failed; the message names the render error. | Start a new export job; if it repeats, report the job id to the operator. |
EXPORT_TOO_LARGE |
422 | No | The service sheet exceeds the 2,000-row bound. | Narrow the export — a smaller date range or tighter filters — or use the data-export feature. |
Privacy, platform, launch & demo
| Code | Status | Safe to retry? | When you see it | What to do |
|---|---|---|---|---|
PRIVACY_CASE_INVALID_TRANSITION |
422 | No | The privacy request is not in the state the command needs: verify needs RECEIVED, completion needs VERIFIED, and restriction applies to DELETION cases for venue guests only. | Follow the case flow: RECEIVED → VERIFIED → COMPLETED or RESTRICTED. |
PRIVACY_HOLD_ALREADY_RELEASED |
409 | No | The legal hold was already released. | Nothing to do — the hold is already lifted. |
PRIVACY_TOMBSTONE_UNKNOWN_ENTITY |
500 | No | An erasure named an entity type outside the tombstone registry. | Internal misconfiguration — report the requestId. |
PLATFORM_INTAKE_SUSPENDED |
422 | No | The venue is platform-suspended for NEW intake — refused on every booking path (guest, walk-in, import). Management surfaces are unaffected. | Contact the platform operator; the suspension is an operator decision. |
VENUE_INTAKE_ALREADY_SUSPENDED |
409 | No | Suspending a venue whose intake is already suspended. | No action needed — the state already matches. |
VENUE_INTAKE_NOT_SUSPENDED |
409 | No | Resuming a venue whose intake was not suspended. | No action needed — the state already matches. |
SUPPORT_CASE_INVALID_TRANSITION |
422 | No | The support-case ladder is OPEN → ESCALATED → RESOLVED; escalating an escalated case or resolving an open one directly is refused. | Resolve only ESCALATED cases; escalate only OPEN ones. |
CAPABILITY_NOT_ACTIVATABLE |
422 | No | Enabling a capability in production while the provider is sandbox or missing. | Complete the approved production provider configuration first. |
PUBLICATION_NOT_PENDING |
409 | No | Deciding a publication review that is no longer PENDING — it was already decided. | Reload the review; the decision was already recorded. |
READINESS_SIGNATURE_INVALID |
500 | No | A stored launch-readiness record failed its signature verification on read — the system fails closed. | Do not act on that record; report it to the operator for investigation. |
DEMO_UNAVAILABLE |
404 | No | The landing demo is switched off by its kill switch, or no published demo venue resolves. | Try later; the operator controls the demo switch. |
Message delivery outcomes (not HTTP errors)
Message delivery records one evidence row per attempt. These outcomes are delivery facts, not error codes, but they explain “the email never arrived”:
| Outcome | Meaning |
|---|---|
SENT |
A real provider effect happened, with a provider message id. |
RECONCILED |
Crash recovery proved the send happened — from prior evidence or a provider lookup. |
SUPPRESSED |
A policy verdict, with the reason recorded: an edit superseded the event (STALE_EVENT), the reminder landed after service (REMINDER_PAST_SERVICE), the booking was already reconfirmed (ALREADY_RECONFIRMED), the venue’s quiet hours apply (QUIET_HOURS), or the waitlist entry closed (ENTRY_CLOSED). |
NO_RECIPIENT |
An honest absence — no address exists in the frozen snapshot. No address is ever invented. |
FAILED |
Aimed at a real recipient but errored; an error code is mandatory. During the pilot every send dead-letters here visibly because no email or SMS provider is configured — a manager can retry a FAILED job once a provider exists; anything else answers CONFLICT. |
SMS adds its own gate ladder, each refusal evidenced: SMS_VENUE_DISABLED, NO_RECIPIENT, SMS_COUNTRY_NOT_APPROVED, SMS_ROUTE_NOT_APPROVED, SMS_QUOTA_NOT_APPROVED, SMS_COST_CAP_EXHAUSTED.
State machines behind the codes
These lifecycles explain most “the record refuses my action” errors. A record only ever accepts the action its current state allows; stale reads answer with the matching *_STALE code.
Reservation status
| State | Meaning |
|---|---|
REQUESTED |
Awaiting a staff decision. Deadline: the earlier of 24 hours after the request and the service start. Undecided requests expire (REVIEW_DEADLINE or SERVICE_START). |
HELD |
A deposit booking holding its tables for a 10-minute checkout. Unpaid holds expire (PAYMENT_HOLD_EXPIRED) and release the tables. |
CONFIRMED |
The bookable truth. Can be rescheduled, cancelled, or marked no-show after the accepted policy’s grace window. |
CANCELLED |
Cancelled by guest or staff; tables released. |
DECLINED |
A request declined, always with a reason and a decision time. |
EXPIRED |
A request deadline passed, a hold was not paid, or a waitlist offer was lost. |
NO_SHOW |
Marked only once the accepted policy’s grace window fully elapsed past the start — earlier attempts answer RULE_VIOLATION. |
COMPLETED |
Exists in the vocabulary and in historical reads, but current writes never set it — records end at CONFIRMED, NO_SHOW, CANCELLED, EXPIRED or DECLINED. |
free booking (instant approval) ─────────────────────────────► CONFIRMED
request booking ──► REQUESTED ──approve──► CONFIRMED
│──decline──► DECLINED
└──no decision by deadline──► EXPIRED
deposit booking ──► HELD (10-minute checkout hold) ──capture──► CONFIRMED
└──not paid within 10 minutes──► EXPIRED
CONFIRMED ──cancel──► CANCELLED
CONFIRMED ──no-show, only after the grace window──► NO_SHOW
Every transition is version-guarded (RESERVATION_STALE on a stale read) and audited with field names only.
Visits and table occupancy
The visit row is created lazily on the first arrival — there is no pre-created row to clean up.
(no visit row) ──first arrival──► PARTIALLY_ARRIVED ──everyone arrived──► ARRIVED
│ corrections adjust the count (0 → NOT_ARRIVED)
ARRIVED ──seat──► SEATED ──complete & clear──► DEPARTED (terminal)
- Arrivals can never exceed the party size — an over-cap arrival answers
RULE_VIOLATION. - Occupancy (physical table state) is
SEATEDorCLEANING. It is created on seating and released only by the explicit complete-and-clear departure. - A timer never frees a table. A party that overruns its planned end keeps blocking that table (
OCCUPANCY_CONFLICTnames the holder) until staff complete-and-clear the visit.
Waitlist entries and offers
Entry: join ──► WAITING ──host offers──► OFFERED ──accept within 5 min──► CONVERTED
▲ │
│ ├── 5 minutes pass (sweep) ──► back to WAITING, place kept
└── guest leaves ──► LEFT
│ └── host cancels ──────────► back to WAITING, place kept
Offer: OFFERED ──► ACCEPTED | EXPIRED | CANCELLED (one live offer per entry)
- An offer is a real held reservation on the slot grid for exactly 5 minutes.
- When an offer expires, the held reservation expires, the tables are released, and the entry returns to WAITING keeping its original place (its join time is untouched).
- Acceptance converts the SAME held record into the confirmed booking — a second booking is never created.
Refund intents
REQUESTED ──approved──► PENDING_PROVIDER ──provider settles──► SUCCEEDED
│ │
└──declined──► CANCELLED └──provider refuses permanently──► FAILED (terminal)
- Every human refund starts as REQUESTED, bounded by the refundable balance (
REFUND_EXCEEDS_AVAILABLEis refused before any write). - The venue’s threshold decides authority: at or below it the requester may act alone; above it a separate ownership-tier approval is recorded — the approver can never be the requester. With no threshold configured, every human refund needs that second approval.
- Until the provider settles, the money shows as PENDING_PROVIDER — never displayed as refunded. A timed-out provider call is recovered by retrying the SAME refund, never a second one.
Background job states
| Job | States | What it means for you |
|---|---|---|
| Import job | STAGED → COMMITTING → COMPLETED | The commit step is explicit; nothing is booked during the dry run. |
| Import row | STAGED → VALIDATED or REJECTED, then COMMITTING → COMMITTED or FAILED | Rejected and failed rows always carry their error text. |
| Export artifact | QUEUED → READY or FAILED | Downloading while QUEUED answers EXPORT_NOT_READY (retry); FAILED answers EXPORT_FAILED (start a new job). READY files stay downloadable for 24 hours, after which they read as NOT_FOUND. |
| Outbox job | READY → RUNNING → DONE, FAILED or CANCELLED (RUNNING returns to READY on retry) | Background work is at-least-once: a crashed worker’s lease expires and the work is reclaimed. Handlers are idempotent, so a retry never double-sends or double-charges. After its attempt budget (8 by default; reminder jobs budget 24 so quiet-hour retries fit) with capped backoff the job dead-letters as a visible FAILED — never a silent drop. |
For symptom-first answers, see Troubleshooting by scenario. Field names, formats and limits are listed in Field reference, and the honest list of what runs today sits in What works today & known limits.