Proactive support — opening a ticket before the customer does
The only capability that contacts a customer who did not contact you — what triggers it, the 15-minute sweep, the ceilings, the dry run showing exactly who would hear from you, and how to switch it off.
Everything else in this addon answers somebody who chose to open a conversation. This opens a ticket on a customer's behalf and sends them the normal support notification about it, because a deposit or a withdrawal of theirs failed.
aiSupportProactiveEnabled ships off. It is the only outward-facing
capability in the addon, and switching it on is a decision about how your
platform speaks to people rather than a tuning change. Run the preview described
below before you touch the switch.
The switch is at Admin → AI Support → Settings → Channels → Outbound, labelled
Open tickets on a customer's behalf. The assistant's own master switch
aiSupportEnabled must be on as well — with the addon disabled there is nothing
to be proactive with.
What sets it off
Rows in the transaction table where all of these are true:
| Condition | Value |
|---|---|
type |
DEPOSIT or WITHDRAW |
status |
FAILED or REJECTED |
updatedAt |
within the last 45 minutes |
updatedAt |
at least 5 minutes ago |
Nothing else. Not a rejected KYC, not a failed order, not a stuck P2P trade — deposits and withdrawals only, because those two are the highest-volume tickets on the platform and the transaction row already knows what happened, minutes before the customer notices.
The five-minute floor is a settling time and it matters. Gateways retry and chain
scanners re-check; a row can read FAILED for a minute and then complete. "We
noticed your deposit did not complete", arriving in that minute, is a false alarm
about somebody's money — the most expensive message this product can send. The
row is re-read one more time immediately before the ticket is written, and if it
has settled by then the ticket is abandoned.
It sweeps rows rather than hooking the transition
Transactions are moved to FAILED or REJECTED from many places — gateway
callbacks, admin actions, chain scanners, the withdrawal queue, and each addon's
own flows. Hooking every one of them means finding every one of them, and being
wrong is silent: the path nobody remembered simply never notifies.
Reading the rows is indifferent to which code path wrote them, including code
paths that do not exist yet. The practical consequence for you is that a new
payment gateway needs no wiring for this to cover it, and that a transaction
which never reaches FAILED/REJECTED — one that sits at PENDING forever —
is invisible to this feature no matter how obviously broken it is.
The cron
aiSupportProactiveSweep, every 15 minutes, against a 45-minute lookback.
The overlap is deliberate: a tick that is missed or runs slow must not leave a
gap that silently drops a customer's failed withdrawal. Duplicate work is not a
problem because every ticket carries the tag txn:<transactionId> and a
transaction that already has one is skipped. See
Scheduled jobs for the whole cron table.
Ceilings, and what happens at them
The failure mode this feature exists for is an outage, which is exactly when the count is highest and the individual message is least useful.
| Limit | Value |
|---|---|
| Failed transactions read per sweep | 250 |
| Tickets opened per sweep | 25 |
| Tickets per customer per rolling 24 hours | 2 |
| Tickets per install per rolling 24 hours | 100 |
The first two are different limits and the distinction cost customers their
message once. The 25 used to be the SQL LIMIT, ordered newest-first, with the
per-transaction dedupe running afterwards in the loop — so a row already
messaged about still consumed one of the 25 slots, and every tick re-read the
same newest twenty-five. Thirty withdrawals failing inside five minutes reached
twenty-five customers; the other five aged out of the 45-minute window in
silence, and they were the ones whose withdrawal failed first.
Now the scan is bounded separately at 250, the 25 applies to tickets actually
opened, and the query is ordered oldest first so the next tick resumes where
this one stopped. A row deferred by the per-sweep ceiling is recorded as
sweep_cap and is still in the window 15 minutes later.
Hitting a ceiling is logged as a warning, not silently truncated — a truncated run reads as "we contacted everyone affected" when the opposite happened. A full 250-row page fires the same warning, because "there were more than we could read" is the fact that turns a quiet log into a status-page decision. A high count here usually means an incident, and a status notice reaches people faster than one ticket each.
The count query behind the two daily ceilings fails closed. If it cannot run, it reports the cap as reached, so a broken query can never become an unbounded mail-out.
See exactly who would be contacted, first
The Preview control sits directly under the switch on the Channels tab, and it works while the feature is switched off — which is the only time it is useful.
-
Open Settings → Channels and scroll to Outbound.
-
Press Run preview. The dry run executes the same query, the same settling window and the same three ceilings as the live sweep.
-
Read the badges. Scanned is how many failed transactions were in the window; would contact is how many tickets this would have opened. Every skip is named rather than counted in one lump — on a healthy install most of them are
already_opened. -
Read the candidates. Up to ten are listed with the customer's name or email, the type and amount, how many minutes ago it failed, and the failure reason recorded on the row.
-
Decide. If the list is people you would want to hear from you, turn the switch on. If it is the same three customers eight times over, it is an outage, and a status notice is the better answer.
Nothing is written, no email is sent, and no model is called. That last one is deliberate: the decision being made is about the selection, and generating prose for a preview would bill you for messages you may never send. So the preview shows who and about what, and never the wording — the wording is written per ticket at send time.
What it actually creates
For each transaction that survives every check:
- A real support ticket. Subject "About your deposit" or "About your
withdrawal", status
REPLIED, importanceHIGHfor a withdrawal andMEDIUMfor a deposit, taggedtxn:<id>,proactiveandcat:depositsorcat:withdrawals. It appears in your desk queue like any other ticket. - One agent message and no customer message. The customer has not written anything, and fabricating a "client" message would put words in their mouth and corrupt every first-response figure on the desk.
- The normal support notification, so it lands in the customer's inbox rather than sitting unread until they complain — which is the outcome the feature exists to prevent.
- An AI session and a turn row. Without a session, the ticket the customer replies to has no assistant attached and nobody picks the conversation up.
The message body is generated from the transaction's own details and whatever your documentation says about that kind of failure. It is instructed to give a reason only if one is recorded on the row, and never to invent a timeline.
Every customer-facing answer in this addon passes a post-filter that catches "we'll refund you" and "your funds will be returned" — a promise you may be legally bound by and did not authorise, on the one topic where a model is most tempted to make it.
A flagged message is thrown away and replaced with the safe fallback: "We
noticed that your {deposit} did not complete. A member of our team is looking
into it and will update you here." The ticket still opens, because a human
seeing a failed transaction early is the point; it just says nothing the platform
cannot stand behind. The discard is logged, and the turn row records
refund_promise_discarded.
It spends budget like anything else
Each ticket costs one retrieval and one model call, and both are recorded on an
ai_support_turn row — one of the two tables the daily and monthly ceilings are
summed from. So this shows up in the spend chart on Overview and counts against
your caps like any answer. A job that generated without recording would be money
the cap cannot see, on the one surface that runs unattended.
It also stops at them. The sweep reads the budget status before doing anything and returns immediately when the ceiling is reached, so a bad day cannot be made worse by an unattended job. A preview still runs at that point, because a preview costs nothing.
See Cost and budgets for how the two windows are measured.
Turning it off
Flip the switch on Channels → Outbound. The next sweep returns without doing anything, within 15 minutes; there is nothing queued to drain.
What does not happen:
- Tickets it already opened stay open. They are ordinary support tickets and your team owns them. Nothing is retracted, and the customer has already been notified.
- Notifications already sent stay sent. There is no recall.
- The tags stay. Because dedupe is on the
txn:<id>tag, a transaction that was already messaged about is still skipped if you switch the feature back on later. Nobody gets a second message about the same failure.
If you want to find what it opened, search your ticket list for the proactive
tag. Those tickets close, reply and escalate exactly like any other.