Offload cases are async.Documentation Index
Fetch the complete documentation index at: https://docs.offloadapi.com/llms.txt
Use this file to discover all available pages before exploring further.
POST /cases only stores the case and queues work. The actual outreach, follow-up, and reply analysis happen later in the worker.
Statuses
| Status | Set by | Meaning |
|---|---|---|
CREATED | POST /cases | The case was accepted and queued. No outreach has been sent yet. |
RUNNING | Worker | Offload has sent the initial outreach or is actively managing the thread. |
INPUT_NEEDED | Worker | Offload cannot continue without a human answer from your app. |
COMPLETED | Worker | The goal was achieved and result is available. |
FAILED | Worker or AgentMail event handler | The case cannot continue. |
End-To-End Flow
What Happens After Creation
POST /casesstores the case withstatus: "CREATED"andattemptCount: 0.- The API enqueues a
CASE_CREATEDworker event. - The worker generates the initial outreach.
- If the send succeeds:
statusbecomesRUNNINGattemptCountbecomes1nextActionAtis scheduled usingfollowUpDelayHours
- If the send fails with a terminal provider error, the case becomes
FAILED.
How Follow-Ups Work
The code runs a cron job every hour to find dueRUNNING cases and queue follow-up work.
When a follow-up event is processed:
- if the counterparty already replied, Offload clears the schedule and waits for reply evaluation
- if no reply exists and the follow-up budget is exhausted, the case becomes
FAILEDwithresultStatus: "max_retries" - otherwise Offload sends another email and increments
attemptCount
Retry Counting
attemptCountcounts outbound messagesmaxAttemptscounts only automated follow-ups after the first message
- after initial outreach:
attemptCount = 1 - after two follow-ups:
attemptCount = 3
How Reply Evaluation Works
When AgentMail reports a reply in the tracked thread, the worker evaluates the thread and the latest reply. The evaluation can lead to:GOAL_ACHIEVED-> case becomesCOMPLETEDFAILED_REJECTED-> case becomesFAILEDINPUT_NEEDED-> case becomesINPUT_NEEDEDand emitscase.input_neededREQUIRES_REPLY-> Offload replies in-thread and staysRUNNINGSNOOZE-> Offload schedules a later recheck and staysRUNNING
Human-In-The-Loop Flow
When the worker needs your input:- the case becomes
INPUT_NEEDED - Offload stores
inputRequest,inputRequestId,inputRequestStatus: "PENDING", andinputRequestedAt - Offload sends
case.input_neededifclientWebhookUrlis configured - your app submits an answer with
POST /cases/{id}/input - the API accepts the answer asynchronously and queues worker processing
case.completed, case.failed, or case.input_needed.
Terminal Outcomes
Known terminalresultStatus values produced by the implementation include:
status | Known resultStatus values |
|---|---|
COMPLETED | goal_achieved |
FAILED | max_retries, failed_rejected, BOUNCED, COMPLAINED, REJECTED |
resultStatus as an open string rather than a closed enum. The current code mixes lowercase workflow values and uppercase provider-delivery values.
Async Guarantees
Internal worker processing is closer to at-least-once than exactly-once:- events are queued through SQS
- the worker claims events by idempotency key
- completed and in-progress duplicates are skipped
- Offload sends a single outbound HTTP request
- a failed webhook is only logged
- the application does not retry delivery