Every Message Goes Out Once
AssistJet stores one unique key before the first step of each run, so a retry stops right there.
Billing and Collections
Reminders are the first job to slip when a month gets busy. AssistJet sends each one on time, answers the questions that come back, and asks you before it offers a payment plan or a credit.
You export the aging report, work down it by hand, and write each reminder. A customer replies with a question, and that reply goes to a different inbox. The next reminder goes out anyway, and now you are chasing a customer who already answered.
Each tool has one level for each seat, and your rules are enforced in code before the call. These tools are examples, and your own list comes from your app.
invoices_getThe run reads the invoice.Readpayments_listThe agent reads the history.Readinvoices_resendA copy goes out on request.Autocredits_createYou approve any credit.Ask Firstaccounts_pauseThe run pauses the account when that is your answer.AutoThe first reminder uses no model at all, so a month of on-time customers costs you 0 tokens.
Write the job in plain words, and the agent drafts these steps as a card. If you build apps for other people, ship the same steps as a recipe, and every customer gets the job with no setup.
[
{
"id": "late-invoice",
"name": "Late invoice",
"trigger_kind": "time",
"trigger": {
"collection": "invoices",
"field": "due_at",
"offset_minutes": 4320
},
"steps": [
{
"kind": "tool",
"tool": "invoices_get",
"arguments": {
"id": "{{record.id}}"
}
},
{
"kind": "condition",
"field": "result.status",
"op": "equals",
"value": "open"
},
{
"kind": "send",
"to": "{{record.email}}",
"subject": "Invoice {{record.number}} is past due",
"text": "Your invoice for {{record.total}} is past due. You can pay it from your account."
},
{
"kind": "delay",
"minutes": 10080
},
{
"kind": "tool",
"tool": "invoices_get",
"arguments": {
"id": "{{record.id}}"
}
},
{
"kind": "condition",
"field": "result.status",
"op": "equals",
"value": "open"
},
{
"kind": "agent",
"instructions": "Read this customer's payment history. Write a polite, firmer reminder that fits it."
},
{
"kind": "send",
"to": "{{record.email}}",
"subject": "Second notice: invoice {{record.number}}",
"text": "{{agent}}"
},
{
"kind": "delay",
"minutes": 10080
},
{
"kind": "ask",
"text": "Invoice {{record.number}} is still open after two reminders. What should we do next?",
"options": [
"Send a final notice",
"Pause the account",
"Give them more time"
]
},
{
"kind": "branch",
"field": "answer",
"op": "equals",
"value": "Pause the account",
"then": [
{
"kind": "tool",
"tool": "accounts_pause",
"arguments": {
"id": "{{record.account}}"
}
}
]
}
]
}
] AssistJet stores one unique key before the first step of each run, so a retry stops right there.
A step that calls a tool or sends a template uses no model, so it costs 0 tokens.
You see the exact call before anything happens. You approve it once, and it runs once.