Dispatch logodispatch
‹ All use cases
Outreach logging · inbound

The write-back lives
where the work already is.

Reps read the morning list in Slack. So the way they log a call is a slash command in that same channel, and it writes straight to the CRM. No tab to open, no screen to build, and not one model call in the path.

Steps9 Model calls0 of 9 End to end3.4s Cost / run$0
In the channel the report lands in
/outreach-tap dana@example.org “left voicemail, retry Tue”
3.4 seconds later
✅ Tapped 1 contact — [2026-04-14] left voicemail, retry Tue
• dana@example.org Ephemeral — only the rep sees it
01

The reason logging gets skipped is the tab, not the typing

Every CRM already has a notes field. The work still doesn't get logged, because logging it means leaving the thing you were doing.

What normally happens

The list arrives in one place; the record lives in another.

  • read the list in chat
  • make the call
  • open the CRM, find the contact
  • … log it later, or never

What happens here

The same surface carries the list and the write-back.

  • read the list in chat
  • make the call
  • type one line in that channel
  • the note is on the record
The list is delivered to a channel, so that channel is where the write-back belongs. Nothing was built for a rep to look at: the interface is a message box they were already typing in, and the confirmation is a reply only they can see. A screen nobody has to learn is a screen nobody has to maintain.
02

Nine steps, and not one of them thinks

A command arrives, a note lands on the right record, a reply goes back. Every step is a tool. There is no prompt anywhere in this pipeline.

01

Split the command

jq · target + message

First word is the target, the rest is the note. Straight quotes, curly quotes and stray whitespace all parse, because a phone will send curly ones.

deterministic
02

Read the rep map

assets · read

A small JSON file mapping each chat account to a CRM user. Editing it is how a new rep gets access.

deterministic
03

Resolve who is asking

merge_by_key · inner join

An inner join against that map. A chat account that isn't in the file produces no row, so everything downstream has nothing to act on. The refusal is the shape of the data, not a rule someone remembered to write.

deterministic
04

Read today's list

assets · read

The same CSV the morning report wrote. One snapshot, shared by the report and the write-back.

deterministic
05

Narrow it to that rep

merge_by_key · inner join

A second join does two jobs at once: it keeps only that rep's rows, and it stamps the parsed command onto each one, so later steps need nothing but the row.

deterministic
06

Pick the contacts, once each

jq · filter + dedupe

The list is one row per site; a note belongs to a contact. A contact who manages four sites appears four times and must still be written once.

deterministic
07

Write the note

CRM · add note, on behalf of the rep

One call per contact, dated and prefixed. It also moves that account's last-activity clock, which is what drops it off tomorrow's stalled list.

× per contactwrite
08

Build the confirmation

jq · compose reply

Counts what was written and names each contact. When nothing matched it says so plainly instead of claiming success.

deterministic
09

Reply to the rep only

http · the command's reply URL

Back to the person who typed it, in the channel they typed it in, visible to nobody else.

write
Step 03 and step 05 are the whole control flow. The engine has no if-statement, and this pipeline needs none: an inner join is a filter and a permission check at the same time. Unknown sender, wrong rep, contact not on today's list — each one simply produces zero rows.
03

Who may write what, decided by a join

A write-back has to answer three questions before it touches anything: who is asking, whose records may they change, and what is it allowed to say happened.

Who is asking

From the request, not the message

The sender's identity comes from the signed request itself, not from anything they typed. You cannot claim to be another rep by wording the command differently, because the command text is never consulted for identity.

a mapped account, or nothing happens
Whose records

Only the ones on their list

The join narrows to that rep's rows in today's list. A contact belonging to someone else cannot be reached, even by naming it exactly, because it was never in the set being written to.

scoped to today's own list
What it says happened

Logged as automation, not as the rep

The note is written with the rep's authority but recorded under the account the automation authenticates as. It is never filed as though the rep typed it into the CRM by hand.

authority yes, impersonation no
This is the part a model in the loop makes worse rather than better. Asked to “log that I called Dana”, an agent has to work out which rep is speaking, which contact is meant, and whether that write is permitted — and it will answer confidently either way. Here none of those are judgements. Identity is a signature, scope is a join, and attribution is a fixed field.
04

What it buys to have no model in the path

Measured on a real run: nine steps, one note written, a reply delivered.

0model calls

No prompt to tune, no output to validate, nothing to re-run when a model changes.

3.4send to end

Fast enough that the rep is still looking at the channel when the confirmation arrives.

Sameevery time

Identical input, identical writes. A pipeline with no model has no temperature.

Noneto build

No screen, no form, no app for anyone to install or be trained on.

Worth being precise about the cost claim, because it is a different one from the rest of this site. Elsewhere a run is cheap because the models are local. Here there is no model to run at all — so the question of tokens, context windows and model choice never arises for this pipeline. It is the cheapest kind of automation there is, and it is the kind most often skipped, because “log the call” sounds too small to automate until you count how often it doesn't happen.
05

What the platform gives you — and what you wire up

The inbound half is a built-in. What you decide is the command, who may use it, and what it writes.

Dispatch provides

Built-in capability — available to every workflow, on every box.

  • signed inbound webhooks as triggers
  • the payload as workflow inputs
  • keyed joins, filters, transforms
  • typed CRM tools + write gating

You wire up

A row in your box's database and one small file. Live on save.

  • the command and its syntax
  • which accounts map to which reps
  • what the note says
  • where the confirmation goes
Nothing on the left is specific to outreach. A signed inbound trigger turns any chat command into a pipeline; a keyed join scopes any write to the rows a person is allowed to touch. The pattern generalises to anything a team currently does by opening a second system — approving, reassigning, marking done.
The takeaway

Put the write-back where the reading already happens.

Shipping today · one line in chat, no model in the path