Skip to content

Transactional channels

Transactional channels carry what a Customer bought, what changed on their records and their next move, over email, SMS, chat, voice and in-app. Every message in or out lands in one conversation per record.

Transactional channels: in, through the record, out

Filled: what the Customer sees. Hollow: a step inside the product, or a tool outside it.

Capability families

A channel is one more Interface onto the same record. A reply by SMS and a call over the API reach the same gate. An email the product sends is the Event a Watch receives, rendered for a person.

FamilyWhat it gives every product
Delivery sendsWhat was bought, sent: an edition to members, a filed report, a receipt, a file. Once per entitled recipient, from your Domain, with each delivery's state
NotificationsA record the recipient is party to has moved: a draft is ready, a booking changed, a payment failed. Sent on the Event that moved it
A turn by messageA notification that carries the recipient's next move (sign, approve, accept), made by a reply, a single-use link, or the same Verb over the API
Inbound intakeAn address, a number or a chat entry mapped to a Verb. Each message is an Event and runs through the gate like a call: a new case, or a reply in an open one
ConversationsOne thread per record across email, SMS, chat, voice, in-app, the API and MCP. Every send and reply is kept with the record, answers search, and exports with it
TemplatesEach message's wording, generated from the file in your brand and the recipient's language, and changed by a diff
Preferences and consentPer principal and channel: what a purchase covers, what needs an opt-in, quiet hours and digests. An opt-out suppresses the address everywhere
Sending identityYour Domain and numbers, each checked once, with bounces and complaints handled before they harm the next send
Delivery state and fallbackPer message and recipient: queued, sent, delivered or failed, retried, then sent on another channel the recipient allows
VoiceAn inbound call answered, transcribed into the thread and routed like a message; an outbound call only as a delivery or a notification

The line between delivery and campaigns

One rule draws it: a message that delivers what a Customer bought, or answers a record they are party to, is product. A message aimed at someone who did neither is a campaign.

A channel deliversA campaign
Why it is sentThe recipient bought, called, or is party to the recordThe recipient is on a list or in a segment
What starts itAn Event on that recordA schedule or an audience
Where it is keptThe record's conversationOutside the product
What permits itThe purchase, or an opt-in on that channelConsent and rules of its own
For exampleAn edition to members, "your draft is ready to sign", a receipt, a booking reminderA newsletter to people who never joined, a win-back, a nurture sequence, a launch announcement

A campaign tool reads the product's Events across the seam: every send, open, reply, bounce and opt-out. It sends under its own consent, never through a product's conversation.

Lifecycles and states

ThingStatesWhat moves it
A conversationopen, waiting_on_customer, waiting_on_seller, closedA delivery, an inbound message or a Verb opens it; it closes with its record
A message outqueued, sent, delivered, then opened or replied; or bounced, failed, suppressedThe channel's answer, the recipient, and the suppression list
A message inreceived, matched, then attached or opened (a new case); or refused, with a reply that says whyThe address or number it reached, the sender's principal, and the gate
Consent, per principal and channelrequired, opted_in, opted_outA purchase covers delivery; SMS and voice need an opt-in; the recipient changes either by reply, in the portal, or by a Verb
A sending identityadded, verifying, verified, suspendedOne DNS record or number check; a complaint rate past the limit suspends it
A templateFollows the Version it ships inA diff, checked at deploy

A refused inbound message still gets an answer: the sender learns which Verb was refused and its cure, in the channel it wrote from.

What you declare, what the platform does

You declareThe platform does
A thread on a Noun, with its channelsOpens one conversation per record, routes every channel into it, and keeps every message with the record
notify on a state: who, what it says, the move it offersSends on the Event, in the channel the recipient prefers, with a link or a reply that makes the move
A send move with toSends once to each recipient the plan entitles, keeps each delivery's state, and stops on a bounce
An inbound address or number, and the Verb it startsWrites each message as an Event, runs the Verb through the gate, and attaches the reply to its thread
Your Domain and numbersChecks each once, sends from them, and handles bounces and complaints
Nothing for consentHolds what each purchase covers, asks for opt-ins where a channel needs one, honours opt-outs and quiet hours, and refuses at deploy any send with no record its recipient is party to

Example

filings keeps one conversation per Filing, tells the Customer when it is their turn, and opens a Filing from an email.

definition.ts
24 lines
import { define, workflow, agentic } from 'apis.dev'

/** Each Customer's annual report, drafted, signed and filed, charged once when the Customer accepts it. */
export default define({
  name: 'filings', domain: 'filings.apis.dev',
  Filing: {
    company: 'string', year: 'number',
    status: {
      order: [], draft: { in: 'ordered', run: workflow(async ($, filing) => { … }) },
      sign: { in: 'drafted', floor: 'signature' }, file: { in: 'signed', run: agentic('File the signed report with the state') },
      accept: 'filed', reject: 'filed',
    },
    thread: { channels: ['email', 'sms', 'chat', 'voice'] },  // one conversation per Filing
    notify: {
      drafted: { to: 'customer', say: 'Your {year} report is ready to sign', act: 'sign' },
      filed:   { to: 'customer', say: 'Filed. Accept it within 7 days', act: ['accept', 'reject'] },
    },
  },
  inbound: { 'orders@filings.apis.dev': 'Filing.order' },   // an email opens a Filing
  offer: {
    'Filing.order': { price: 25, when: 'accepted', accept: 'customer',
                      acceptWithin: '7d', remedy: 'redo', grade: 'measured' },
  },
})

The Filing's conversation as an agent reads it: the order by email, the turn by email and SMS, and a reply by text.

GET /filings/filing_1/conversation
18 lines
{
  "id": "conv_1",
  "record": "filing_1",
  "status": "waiting_on_seller",
  "messages": [
    { "id": "msg_1", "dir": "in",  "channel": "email", "from": "dana@acme.com",
      "event": "Filing.ordered", "at": "2026-10-24T09:00:00Z" },
    { "id": "msg_2", "dir": "out", "channel": "email", "kind": "notification",
      "event": "Filing.drafted", "act": "Filing.sign", "state": "delivered",
      "at": "2026-10-25T10:02:00Z" },
    { "id": "msg_3", "dir": "out", "channel": "sms", "kind": "notification",
      "event": "Filing.drafted", "act": "Filing.sign", "state": "delivered",
      "at": "2026-10-25T10:02:00Z" },
    { "id": "msg_4", "dir": "in",  "channel": "sms", "from": "dana@acme.com",
      "text": "Can I sign tomorrow morning?", "at": "2026-10-25T10:14:00Z" }
  ],
  "next": null
}

A send aimed past the line fails the check before it deploys.

Terminal
$ npx apis.dev check
✕ notify 'every Mon 09:00' → everyone: a send needs a record its recipient bought, called or is party to.
  A campaign runs outside the product, on its Events.

Contracts with the other subsystems

Channels never decide. An inbound message is a call, and an outbound one is an effect of a commit.

SubsystemWhat channels take from itWhat channels give it
Definition and compilethread, notify, inbound and templates, checked at deploy: a send with no party to it is refusedTemplates as versioned records, changed by a diff
Identity, access and policyThe sender's principal, parties without accounts, single-use links for a turnConsent and preferences as grants, per principal and channel
Model and recordsThe record a conversation belongs toConversations and messages as records, searched and exported with it
Data and SourcesA Customer's own inbox and number, connected as an IntegrationReplies written back to the system they came from
RuntimeThe Event that starts each send; quiet hours and digests as clocksEach send as a durable effect, sent once; each inbound message as an Event
WorkThe case an inbound message opens, and its turnReplies as input to the case; an agent's delegated sending in the thread
CommerceEntitlements: who a delivery is owed to; receipts and invoices to sendDelivery of what was bought, recorded against the purchase
Interfaces and distributionYour Domain and brand; the in-app channel through the UI contractsThe conversation as one more Interface onto the record
Trust and operationsSuppression, complaint limits, residencyThe evidence of every send and every consent

What Customers and their agents meet

  • One thread, every channel. An email reply, a text and an API call about the same case land in one conversation, in order.
  • Their turn arrives with its move. "Ready to sign" carries a link that signs; an agent makes the same move with one Verb.
  • No message they did not earn. A Customer gets what it bought and what its records do, and nothing it never asked for.
  • Preferences that hold. A Customer switches SMS off in the portal, by replying STOP, or by a Verb. Every channel honours it at once.
  • Answers to what they send. An inbound message runs as a call. It opens a case or replies in one, and a refusal comes back with its cure.
  • An agent reads the thread. GET /{noun}/{id}/conversation gives an agent the whole exchange. It replies through the API, and the person sees the reply in email.

Every message is about a record someone bought or is party to, so the product's channels carry only what was earned.