Priya Raghunathan
Why was I charged $70.97 on 12 March?
Design system
21 sections, 21 primitives and 64 tokens, each rendered here from the file the theme ships.
Tokens
Swatches read from src/theme.css, so this page recolours with the theme.
--font-text · Public Sans · self-hosted, OFL 1.1
Relay closes routine billing tickets
--font-mono · JetBrains Mono · self-hosted, OFL 1.1
stripe.subscriptions.retrieve · sub_1P7dK2 · 212 ms · +17m04s · $70.97
rounded-themed · --radius 0.625rem
rounded-md · the nested step down, 6px
Hairlines never carry structure alone. Every bordered surface also changes background by at least 1.14:1, which is why the border token is tuned for calm rather than contrast.
The thirty-eighth colour is --critical-border. Relay is flat material and ships no shadows; --shadow-color stays declared and unspent.
Primitives
Sections never import each other, so anything shared lives in components/ui.
src/components/ui/*.tsx
ui/button
Disabled dims the variant and takes the pointer away; pending is a different thing — the control keeps its label, moves to the info surface, sets aria-busy and refuses a second press.
ui/unavailable-action
Sample console. Approvals run in your helpdesk.
Not a disabled button. A disabled control says something you do could turn it on; these are a picture of the product, so they are a role="button" with aria-disabled, no tab stop, and a dashed edge that says so without an attribute inspector.
ui/badge
ui/section-heading
The boundary
Written down before you switch it on, and enforced in code.
ui/tool-call
Billing policy §3 · match 0.91
ui/chat-turn · three roles, one streaming
Why was I charged $70.97 on 12 March?
That charge is the mid-cycle switch to Studio, and
Drafting a reply
Refund approved and the plan moves back on 1 April.
ui/mode-toggle · one of the theme’s three client islands
ui/wire-figure renders the two .wire.json graphs in the agent-flow and scope-ledger frames below; ui/logo, ui/container and ui/page-hero render there too
ui/template-summary · automated, approval and handoff
Selected workflow
Confirm the duplicate, apply a permitted account credit and send its receipt.
Close condition
Close after the $24 credit posts and its receipt sends.
Selected workflow
Explain the charge, stop at the refund ceiling and hand over the researched case.
Close condition
Close only after Dana approves the $70.97 refund and replies.
Selected workflow
Observe one buyer-owned retry, then send its receipt or preserve a failure handoff.
Close condition
Close after the buyer retry succeeds and its paid receipt reply sends.
Selected workflow
Gather the certificate, verify required fields and wait for a tax specialist.
Close condition
Close after a specialist approval note appears and the confirmation reply sends.
ui/boundary-matrix · allowed, conditional and denied
Boundary matrix
Sample frontend state. Connect these names to your own policy and tool layer.
| Permission | Tool | Rule |
|---|---|---|
| Allowed | stripe.charges.list | Read exact amount, invoice and capture time |
| Allowed | postgres.accounts.read | Read account status and current credit balance |
| Conditional | stripe.credits.write | Exact duplicate proven and credit at or below $50 |
| Denied | stripe.refunds.create | Cash refunds require a person |
ui/example-run · success, policy denial, tool error, approval timeout and escalation
Example run
Ordered fixtures show why this path closes or stops. No tool executes here.
Sample ticket received
ZD-5198 enters the billing queue
Exact duplicate confirmed
Two $24 captures share one invoice and capture window
Credit posted
$24 account credit and receipt recorded
Example run
Ordered fixtures show why this path closes or stops. No tool executes here.
Sample ticket received
Priya asks about the $70.97 proration charge
Policy denied the refund
Billing policy §3 stops a refund above $50
Research handed over
Dana receives the invoice, arithmetic, policy and draft
Example run
Ordered fixtures show why this path closes or stops. No tool executes here.
Sample renewal checked
$149 invoice remains open after a card decline
Buyer retry succeeded
The payment recovery service reports the invoice paid
Reply tool failed
helpdesk.replies.write returned a temporary tool error
Failure handed over
Billing receives the invoice, decline and failed call ID
Example run
Ordered fixtures show why this path closes or stops. No tool executes here.
Sample evidence complete
Certificate issuer, jurisdiction and expiry are present
Approval timed out
No specialist decision arrived; complete evidence stays in the tax queue
Example run
Ordered fixtures show why this path closes or stops. No tool executes here.
Sample evidence reviewed
Issuer and expiry are present, but the jurisdiction is ambiguous
Relay chose a person
Relay stops before approval because the jurisdiction needs interpretation
ui/handoff-contract · ready, complete, missing context and retry
Handoff contract
Evidence attached
Owner
Billing on-call
Only used when the duplicate match is uncertain
Recovery
A person confirms the match before money moves.
Handoff contract
Evidence attached
Owner
Dana Okonjo · billing on-call
Billing review queue
Recovery
Dana approves, edits or declines the refund from the researched brief.
Handoff contract
Evidence attached
Owner
Billing on-call
Payment recovery queue
Recovery
A person sends the paid receipt after the helpdesk tool recovers.
Handoff contract
Evidence attached
Owner
Tax specialist
Tax evidence queue
Recovery
The specialist records a decision after the sample approval window.
Handoff contract
Evidence attached
Owner
Tax specialist
Tax evidence queue
Recovery
The specialist supplies the jurisdiction before requesting approval.
ui/template-checklist · sample, configured and frontend only
Buyer checklist
Charge reads
Replace the two sample charge records
Credit ceiling
$50 matches the shipped policy sample
Execution
Connect your Stripe write and receipt job
src/components/ui/field.tsx
rest · the hint, and the optional marker in words
Where the answer lands.
Each one publishes its own owner and reply window.
What Relay did or did not do, and on which account.
invalid · the error replaces the hint on the same id
That address has no domain. Add one and try again.
Choose the desk this should reach.
Say a little more. The desk answers faster with the account name in it.
valid · a checked value, and a durable receipt under it
The domain answers.
Security answers against the scope ledger, in two working days.
Enough for the desk to open on.
read-only · the value still matters, so it stays legible and selectable
Taken from the workspace. Change it in settings.
Routed by the workspace's billing owner.
Copied from ticket ZD-4471 when it escalated.
disabled · out of play on a surface of its own, never the resting one at half opacity
Nothing to reply to until a workspace is connected.
The desks arrive with the workspace.
Connect Stripe and a helpdesk first.
submitting · ui/form, the whole fieldset inert
success · ui/form, the receipt focus lands on
Recorded for Billing and accounts.
Held in this page. Nothing reached billing@relay.systems.
Keyboard: the label points at the control, and the one message line under it is named by aria-describedby whichever of the three sentences it is carrying — so a reader who tabs in hears the error, or the receipt, or the hint, and never two of them. Read-only keeps its tab stop and stays selectable; disabled does not, and says so as a surface rather than as half an opacity.
src/components/ui/form-error-summary.tsx
error-summary · two refused fields, and ui/form's invalid state
invalid · the same submit, with one long message wrapping
Keyboard: the panel is inserted on a refused submit rather than revealed — an always-mounted empty role="alert" announces on every keystroke and says nothing on the submit that matters. It takes focus at tabindex="-1" one render after the refusal, when it exists, and each row is a real link to the field it names.
src/components/ui/accordion-disclosure.tsx
closed
In config, as a number per rule — not in a prompt. Set it to zero and every refund goes to a person with the draft already written.
Anyone with the admin scope, and the record names every field the agent read. It is the same log the weekly report is built from.
open
In config, as a number per rule — not in a prompt. Set it to zero and every refund goes to a person with the draft already written.
Anyone with the admin scope, and the record names every field the agent read. It is the same log the weekly report is built from.
Keyboard: Enter or Space opens the row, and Escape from inside the answer closes it and puts focus back on the question — the one thing a bare <details> cannot do, and the reason the part is here. Under prefers-reduced-motion the height tween is off and the answer is simply there.
src/components/ui/toggle-group.tsx
default · one option carrying the selection
disabled · an option this account cannot reach
Unavailable is a surface of its own: muted ground, muted ink. It is not the resting option at half opacity.
Keyboard: this set is a radio group. One tab stop, ← and → move between options, and selection follows focus — the model roving plus a single value derives, so what a screen reader announces and what the arrow keys do cannot disagree.
Sections
Each renders with the content it ships with, and nothing here is a placeholder.
src/components/agent-hero.tsx
Billing queue
Proration, invoice copies and card declines, answered from sample Stripe state with the policy section cited.
Zendesk ZD-4821
There is a $70.97 charge on my card from 12 March. I pay $39.00 a month. What is this?
sub_1P7dK2 · Studio · $149.00/mo
The $70.97 is the mid-cycle switch to Studio on 12 March: $96.13 for the twenty days left, less
Drafting a reply
src/components/outcome-band.tsx
65%
Of billing tickets closed without a human
Our billing queue went from 340 tickets a week to 118 reaching a person.
The same ticket runs through the transcript, the log and the flow diagram on the product page.
src/components/how-it-works.tsx
How it runs
Three steps, and the third is the one your team cares about.
Relay opens the Stripe subscription, the invoice lines and your accounts table before it writes a word. No answer is composed from the ticket text alone.
Every reply names the help-center section it came from. If no section matches above 0.8 confidence, Relay does not send.
Refunds over $50, cancellations and disputes go to a named person with the four facts already gathered and a reply drafted.
src/components/boundary-grid.tsx
The boundary
Written down before you switch it on, and enforced in code rather than in a prompt.
Answered from your data, sent without waiting for anyone.
Every time, with the research attached and a reply drafted.
src/components/integrations.tsx
Sample systems
Read access where it needs it, write access in five places, all listed.
Subscriptions, invoices, charges. Writes credits up to $50 and payment retries.
A read replica of your accounts and entitlements tables. Relay never holds a write credential.
Reads the ticket thread, posts the reply or leaves it as a draft for review.
Per-inbox. Relay can be switched on for billing and left off for everything else.
Reads the conversation, replies in-app, and keeps the assignment when it escalates.
Posts every handoff into the channel that owns the account, with the facts inline.
src/components/conversation-thread.tsx
One ticket
The same ticket the hero opens, played out to the handoff that closes it.
Zendesk ZD-4821 · Coilbook
There is a $70.97 charge on my card from 12 March. I pay $39 a month. What is this?
sub_1P7dK2 · Studio · $149.00/mo
$96.13 Studio, less $25.16 unused Solo
That charge is the switch to Studio on 12 March. Stripe bills the new plan for the twenty days left in the period and credits the same twenty days you had already paid on Solo, so the invoice is $96.13 minus $25.16.
From 1 April it settles at $149.00 a month on the normal date. Your $39 payments were the old plan and have stopped.
That makes sense, thank you. Can I have the $70.97 back and go to Solo? We are not using the extra seats.
blocked · above the $50 ceiling
A refund of this size needs a person. I have passed the thread to Dana Okonjo with the invoice, the plan history and a reply drafted, and told Priya someone will answer today.
Refund approved and the plan moves back to Solo on 1 April. Priya sees $70.97 returned within five working days.
src/components/tool-timeline.tsx
The run log
What Relay touched, in what order, and what came back, with nothing hidden behind a spinner.
src/components/agent-flow.tsx
The decision
One check decides whether a billing ticket ends with Relay or with a person.
trigger
Ticket arrives
A billing question lands in the queue with the customer's account id attached.
lookup
Read the account
Four reads before a word is drafted: Stripe, the accounts replica, the help center, and the ticket's own thread.
check
Does it clear policy?
Two tests in the tool layer, run before every send: the help center match scores 0.8 or better, and no money moves past $50.
clears · 65%
step
Relay answers
ZD-4821 cleared here at +1.44s. The reply cites the policy section it came from, and a credit up to $50 can go with it.
step
Ticket closes
The run keeps every call, input and rule that allowed it for 13 months.
trigger
The customer asks again
ZD-4821 came back at 09:31 wanting the charge refunded, so the same loop ran a second time and met the same check.
returns to Read the account
blocks · 35%
handoff
A person takes it
The second check stopped ZD-4821: a $70.97 refund against a $50 ceiling.
handoff
Dana gets the brief
Four facts and a drafted reply: the account, the charge, the payment history, and the rule that stopped it.
Ticket arrives
trigger · starts the run
A billing question lands in the queue with the customer's account id attached.
Read the account
lookup · after Ticket arrives · also after The customer asks again
Four reads before a word is drafted: Stripe, the accounts replica, the help center, and the ticket's own thread.
Does it clear policy?
check · after Read the account
Two tests in the tool layer, run before every send: the help center match scores 0.8 or better, and no money moves past $50.
Relay answers
step · after Does it clear policy? · branch clears (65%)
ZD-4821 cleared here at +1.44s. The reply cites the policy section it came from, and a credit up to $50 can go with it.
Ticket closes
step · after Relay answers
The run keeps every call, input and rule that allowed it for 13 months.
The customer asks again
trigger · after Ticket closes
ZD-4821 came back at 09:31 wanting the charge refunded, so the same loop ran a second time and met the same check.
A person takes it
handoff · after Does it clear policy? · branch blocks (35%)
The second check stopped ZD-4821: a $70.97 refund against a $50 ceiling.
Dana gets the brief
handoff · after A person takes it
Four facts and a drafted reply: the account, the charge, the payment history, and the rule that stopped it.
src/components/handoff-panel.tsx
The handoff
The account, the charge, the rule that stopped it, and a reply ready to send.
Handed to Dana Okonjo
billing on-call · 09:31Hi Priya, I have approved the $70.97 back to your card and moved you to Solo from 1 April. The refund lands within five working days.
Sample console. Approvals run in your helpdesk.
Stopped by refund.ceiling, not by a hunch.
src/components/guardrails.tsx
Guardrails
Your security review will ask these four questions, so here are the answers first.
The ceiling is a number in your config, checked in the tool layer before the call is made. A prompt cannot argue with it.
Below 0.8 match against your help center, Relay drafts and escalates instead of sending. The threshold is yours to move.
Account data comes from a read replica. There is no path from Relay to a row in your primary database.
Each call is logged with the ticket, the inputs and the rule that allowed it, kept 13 months and exportable as CSV.
src/components/scope-ledger.tsx
Access
Three of these eight scopes can write, and only one of the three moves money.
trigger
Eight scopes are held
Six systems, one credential each: a restricted Stripe key, a read-only replica role, an OAuth app per inbox, and one Slack bot token.
check
Can the scope write?
The credential decides, not the prompt. A read-only role cannot be talked into a write.
reads · 5 of 8
lookup
Five only read
Stripe subscriptions and invoices, the accounts replica, the help center and the ticket thread. Nothing they touch changes.
writes · 3 of 8
step
Three can write
One reply per ticket, one handoff message, and account credits. Every write is scoped to a single object.
check
Does the write move money?
Two of the three writes only post text. The third is the only path from Relay to your balance.
words · 2 of 3
step
Two post text
helpdesk.replies.write posts one reply on one ticket. slack.handoff.write posts into the one channel that owns the account.
money · 1 of 3
limit
One moves money, capped at $50
stripe.credits.write is checked against the ceiling in the tool layer, before the call is made.
handoff
The refund goes to a person
ZD-4821 asked for $70.97 against the $50 cap, so the scope was never used and Dana Okonjo answered instead.
Eight scopes are held
trigger · starts the run
Six systems, one credential each: a restricted Stripe key, a read-only replica role, an OAuth app per inbox, and one Slack bot token.
Can the scope write?
check · after Eight scopes are held
The credential decides, not the prompt. A read-only role cannot be talked into a write.
Five only read
lookup · after Can the scope write? · branch reads (5 of 8)
Stripe subscriptions and invoices, the accounts replica, the help center and the ticket thread. Nothing they touch changes.
Three can write
step · after Can the scope write? · branch writes (3 of 8)
One reply per ticket, one handoff message, and account credits. Every write is scoped to a single object.
Does the write move money?
check · after Three can write
Two of the three writes only post text. The third is the only path from Relay to your balance.
Two post text
step · after Does the write move money? · branch words (2 of 3)
helpdesk.replies.write posts one reply on one ticket. slack.handoff.write posts into the one channel that owns the account.
One moves money, capped at $50
limit · after Does the write move money? · branch money (1 of 3)
stripe.credits.write is checked against the ceiling in the tool layer, before the call is made.
The refund goes to a person
handoff · after One moves money, capped at $50
ZD-4821 asked for $70.97 against the $50 cap, so the scope was never used and Dana Okonjo answered instead.
stripe.subscriptions.read
Stripe
Restricted API key
Plan, period and the proration lines behind a charge
stripe.invoices.read
Stripe
Restricted API key
Invoices, charges and receipts
stripe.credits.write
Stripe
Restricted API key
Account credits capped at $50, the only scope that moves money
postgres.accounts.read
Postgres replica
Read-only replica role
Entitlements, seat counts and trial end dates
docs.search
Help center
Public URL, no key
Published articles, for the citation on every reply
helpdesk.threads.read
Zendesk · Front · Intercom
OAuth app, per inbox
The ticket thread and its history
helpdesk.replies.write
Zendesk · Front · Intercom
OAuth app, per inbox
One reply per ticket, sent or left as a draft
slack.handoff.write
Slack
Bot token, one channel
The handoff message, in the channel that owns the account
Five systems accept a write. The accounts replica is read only, and no scope reaches your primary database.
src/components/audit-record.tsx
Audit
A blocked call keeps the same fields as a sent one, so nothing is missing.
13 months
Kept on every row
Exported as CSV from the console, or read through the audit API with a scoped key.
Processed in eu-west-1. Three sub-processors: the model provider, the cloud host and the error store.
The seven other rows from this run are on the run log. Read all eight
src/components/pricing-table.tsx
Plans
A ticket Relay hands to a person still cost you that person, so it is not billed.
$290per month
500 closed tickets a month
$0.60 per ticket after that
$890per month
2,500 closed tickets a month
$0.42 per ticket after that
Customannual
Unmetered closed tickets
Priced against last quarter's volume
$0
For every ticket Relay hands to a person. Escalations are the ones that still cost you a salary, so they are not on the invoice.
src/components/price-estimator.tsx
Your volume
Move the slider to the tickets you close in a month and the plan follows the number.
Coilbook, the account in the run log, closes about 950 a month.
Cheapest plan
$560a month on Starter
$0.59 per closed ticket
Tickets Relay hands to a person are not counted, so this is the whole invoice.
src/components/support-desk.tsx
Where to write
Each one states who reads it and how long you wait, because that is the product's own claim.
Invoices, proration and anything Relay escalated to a person on your queue.
Dana Okonjo, billing on-call
Connecting Stripe and a helpdesk, moving a ceiling, tuning the 0.8 threshold.
The engineer who built your connector
Scopes, sub-processors and the DPA. Most of it is already written down.
Answered against the scope ledger
Relay sent something it should not have, or stopped sending at all.
The on-call engineer, not a queue
31 min
Median reply on billing, last quarter
Measured from the message arriving to a person answering it, not to an autoresponder. The slowest 5% took under four hours.
Scopes, retention and the sub-processor list are on the security page.
src/components/request-form.tsx
Send it on
Pick the desk, say what happened, and the receipt tells you who has it and how long you wait.
This is the frontend. The request is validated and receipted in the page and nothing is transmitted — point the form at your own endpoint and the same four states carry through.
src/components/template-index.tsx
Workflow library
Filter four sample paths, then inspect tools, stop rules and handoff evidence.
Matches the workflow name, its summary and its tools.
Risk level
4 workflows shown
Selected workflow
Explain the charge, stop at the refund ceiling and hand over the researched case.
Close condition
Close only after Dana approves the $70.97 refund and replies.
Boundary matrix
Sample frontend state. Connect these names to your own policy and tool layer.
| Permission | Tool | Rule |
|---|---|---|
| Allowed | stripe.subscriptions.retrieve | sub_1P7dK2 · Solo $39 to Studio $149 |
| Allowed | stripe.invoices.retrieve | in_9QtL4M · +$96.13 minus $25.16 |
| Allowed | docs.search | Billing policy §3 · refund ceiling $50 |
| Denied | stripe.refunds.create | $70.97 exceeds the $50 ceiling by $20.97 |
Example run
Ordered fixtures show why this path closes or stops. No tool executes here.
Sample ticket received
Priya asks about the $70.97 proration charge
Policy denied the refund
Billing policy §3 stops a refund above $50
Research handed over
Dana receives the invoice, arithmetic, policy and draft
Handoff contract
Evidence attached
Owner
Dana Okonjo · billing on-call
Billing review queue
Recovery
Dana approves, edits or declines the refund from the researched brief.
Buyer checklist
Invoice reads
The fixture uses in_9QtL4M
Policy rule
The $50 ceiling matches Relay's sample
Approval action
Connect your queue and refund executor
Long label
Cross-border tax exemption with an expired certificate and an unresolved jurisdiction rule
No result
No workflows match these filters.
Clear filters returns focus to search.
src/components/faq.tsx
Questions
The answers a support lead needs before they will let an agent near the queue.
Only account credits up to $50, and only when your config allows it. Anything above the ceiling goes to a person with the draft written. The ceiling can be set to zero on day one.
Every reply names the policy section it used, so a wrong answer is traceable to a wrong document. The weekly report lists each reply a customer pushed back on, and you can move the confidence threshold up until it stops.
About an hour. You connect Stripe read keys and one helpdesk, point Relay at your help center, and set the refund ceiling. You approve its first 50 replies before it sends on its own.
It reads the ticket, the Stripe record and a read replica of your accounts table. Nothing is used for training, and the audit log records every field it touched.
There is an HTTP integration for anything with a webhook and a reply endpoint. Zendesk, Front and Intercom are the ones we maintain directly.
Ceilings, thresholds and escalation routes are per-rule config rather than a prompt. Teams with annual contracts usually route those accounts to a person and let Relay take the monthly ones.
src/components/cta.tsx
Connect Stripe and one helpdesk, approve its first 50 replies, then let it send.
src/components/ui/page-hero.tsx
Inside a run
Ticket ZD-4821 arrived at 09:14 and closed at 09:38.