Skip to content

The order started as a DM, so the customer record has to come from the conversation

A multi-tenant inbox for shops that sell through social messages, where a conversation becomes a linked customer, an order and a delivery without the operator opening a second system. The AI suggests replies; a person sends them.

Inbox · all accounts

4 connected accounts, one queue · 09/17/2026

Search threads, ordersOakmoss CeramicsMC

Connected accounts

4 of 4
@oakmoss.ceramicsPhoto app4
Oakmoss CeramicsMessenger2
@oakmoss.secondsPhoto app1
@oakmossstudioShort video1
One queue · newest first8 open

Rosa Delgado10:14 AM

Any chance it arrives by Saturday? It's a gift.Draft · not sent

Hannah Okafor10:11 AM

Do you do gift wrap on the mug sets?

@the.lindqvists10:08 AM

Is the chipped jug still $18?New handle

Devon Marsh9:57 AM

Saw the glazing video, do you sell the tall vase?

Priya Raman9:50 AM

Paid, thank you! Can't wait.OK-1041

@kettle.and.crumb9:44 AM

Do you do wholesale for a café?

Sam Whitfield9:31 AM

Tracking says delivered, thanks!Delivered

Aiko Tanaka9:12 AM

Can I swap the mug color to moss?

RD

Rosa DelgadoLinked customer

@rosa.delgado.home · Photo app · to @oakmoss.ceramics

OK-1043 raised
Hi! Is the large speckled oat bowl back in stock?
10:02 AM#m1
It's Rosa Delgado btw, I bought the bud vases from you on Messenger in March.
10:03 AM#m2
Hi Rosa! Yes, restocked this morning. The large is $64.
Maya · 10:04 AM#m3
Could I get two of those and the 4-plate set in the same glaze?
10:06 AM#m4
Two large bowls at $64 and the 4-plate set at $96 is $224, plus $12 shipping, so $236. Shall I put that through?
Maya · 10:09 AM#m5
Yes please! Same address as last time, 418 Alder St, Apt 3B, Portland, OR 97209.
10:11 AM#m6
Any chance it arrives by Saturday? It's a gift.
10:14 AM#m7
Order OK-1043 raised here from #m5 #m6 · 10:16 AM

Suggested reply · not sentFrom the catalog and the last six messages

Thanks Rosa! Your order is in: 2 × large speckled oat bowl and the 4-plate set, $236 with shipping. Orders placed before 12 PM ship the same day, so it goes out today. Delivery takes 2–4 days, so Saturday is likely.
Sends only when a person presses SendDiscardEditSend as Maya
RD

Rosa Delgado

Customer since 03/14/2025

This thread belongs here

Orders

7

Lifetime

$756.00

Handles

2

Linked handles
Rosa DelgadoMessenger · 6 orders
@rosa.delgado.homePhoto app · linked 10:05 AM

Order OK-1043#m5#m6

2 × Large speckled oat bowl$128.00

1 × 4-plate set, oat glaze$96.00

Shipping$12.00

Total$236.00

Earlier orders
OK-0988Pour-over dripper + 2 cups$112.00
OK-0934Bud vases × 3$78.00
OK-08594-plate set, moss glaze$96.00

What the engagement involved

Industry
E-commerce & Retail
Duration
16 weeks
Cooperation model
Dedicated team
Services
SaaS engineeringAI integrationPlatform
Integrations
Social platform messaging APIsIdentity provider webhooksLanguage model APICourier tracking APIsSubscription billing
Technologies
Next.jsTypeScriptPostgreSQLManaged AuthTailwind CSS
Team
1 Product lead2 Full-stack engineers1 Product designer

Client name withheld under NDA. Engagement details are shown to the extent our agreement permits.

Introduction

What we were brought in to do

The vendor sells software to shops whose whole business runs in social direct messages. We built the platform: a shared inbox across connected accounts, customers linked from the threads themselves, orders and deliveries, and an AI that drafts replies for a person to send.

A software vendor selling to shops whose entire business runs in social direct messages. In those shops the thread is the system of record: the order is in a scroll, the address is four messages up, and only whoever was on the phone that day can answer whether it shipped. Every tool they had tried assumed an order arrives from a checkout.

Full-Stack Engineering

Where the old way broke

In a shop that sells by DM, the thread is the system of record. The order lives in a scroll, the address is four messages up, and the only person who can answer 'did we ship it' is whoever was on the phone that day. Every tool they had tried assumed the order arrived from a checkout.

The consequence is that the business's history lives on a platform the shop doesn't control and can't query. A staff member leaving takes their threads' context with them; an account revoked takes the record. And the operational questions (who is owed what, what's outstanding) can't be answered at all without scrolling, so they mostly go unanswered.

Make the conversation produce the record. Linking a handle to a customer is a single action from the thread, an order can be raised from the messages that describe it, and the delivery hangs off the order, so the thread stops being the only place the truth exists.

Auto-link from the thread

A handle becomes a customer record in one action, so the conversation stops being the only place the customer exists.

A handle becomes a customer record in one action from inside the thread, matching on prior orders and contact details where they exist and creating a record where they don't. That single action is the whole point: until the customer exists outside the conversation, the conversation is the only place the business's history lives, and it disappears with the platform.

What shipped
  • Handle to customer record in one action, from the thread
  • Matches existing customers on orders and contact details
  • The history stops living only inside the conversation
Inbox · @rosa.delgado.home

Photo app · @oakmoss.ceramics · 09/17/2026 10:05 AM

Search threads, ordersOakmoss CeramicsMC
@

@rosa.delgado.homeNot linked

@rosa.delgado.home · Photo app · to @oakmoss.ceramics

Link customer
Hi! Is the large speckled oat bowl back in stock?
10:02 AM#m1
It's Rosa Delgado btw, I bought the bud vases from you on Messenger in March.
10:03 AM#m2
Hi Rosa! Yes, restocked this morning. The large is $64.
Maya · 10:04 AM#m3
Stallbook matched this handle to an existing customer from #m2. Nothing is linked until you choose.
Reply to @rosa.delgado.home…Send

Link @rosa.delgado.home

From inside the thread · no customer form

1 action
RD

Existing customer found

Rosa Delgado

$520.00

6 orders since 03/14/2025

Name in #m2 matches the customer record

“Bud vases … in March” in #m2 matches order OK-0934

What linking writesEach field shows where it came from
NameRosa Delgado#m2
Prior orderOK-0934 · Bud vases × 3 · 03/28/2026#m2
New handle@rosa.delgado.home · Photo appthis thread
Existing handleRosa Delgado · Messengercustomer record
Address on file418 Alder St, Apt 3B, Portland, OR 97209order OK-0988
Link to Rosa DelgadoAdds this handle to the existing record. Creates nothing new.

Not Rosa? Create a customer from this thread · also one action

Once linked, the history lives on the customer record, not only inside a conversation on a platform the shop doesn’t control.

On screen

Linking a handle to a customer in one action: an existing customer matched on the name and prior order mentioned in the thread, and every field showing the message or record it was lifted from.

Inbox · Rosa Delgado

Photo app · @oakmoss.ceramics · 09/17/2026 10:16 AM

Search threads, ordersOakmoss CeramicsMC
RD

Rosa DelgadoLinked customer

@rosa.delgado.home · Photo app · to @oakmoss.ceramics

Raise order
Hi! Is the large speckled oat bowl back in stock?
10:02 AM#m1
It's Rosa Delgado btw, I bought the bud vases from you on Messenger in March.
10:03 AM#m2
Hi Rosa! Yes, restocked this morning. The large is $64.
Maya · 10:04 AM#m3
Could I get two of those and the 4-plate set in the same glaze?
10:06 AM#m4
Two large bowls at $64 and the 4-plate set at $96 is $224, plus $12 shipping, so $236. Shall I put that through?
Maya · 10:09 AM#m5
Yes please! Same address as last time, 418 Alder St, Apt 3B, Portland, OR 97209.
10:11 AM#m6
Any chance it arrives by Saturday? It's a gift.
10:14 AM#m7
Building order OK-1043 from #m5 #m6 · still in this thread
Reply to @rosa.delgado.home…Send

New order OK-1043

Raised beside the conversation · no second system

#m5#m6
RD

Rosa Delgado

Linked customer · 6 earlier orders

Linked
Items, quantities and agreed price#m5
ItemQtyPriceLine

Large speckled oat bowl

OAT-BWL-L · 5 in stock

2$64.00$128.00

4-plate set, oat glaze

OAT-PLT-4 · 3 in stock

1$96.00$96.00

Subtotal$224.00

Shipping, flat$12.00

Total agreed matches $236.00 in #m5$236.00

Agreed by the customer#m6

“Yes please! Same address as last time, 418 Alder St, Apt 3B, Portland, OR 97209.”

Ship to

Rosa Delgado, 418 Alder St, Apt 3B, Portland, OR 97209

#m6
Raise order OK-1043Tags OK-1043 into the thread at #m6. The conversation stays on screen.
On screen

An order built from the two messages that agreed it: items, quantities and agreed price from one, the customer's yes and address from the other, tagged in the thread without another system being opened.

Orders raised in place

An order is created from the messages that agreed it, without switching to another screen.

An order is raised from the messages that agreed it, without leaving the thread: items, quantities and the agreed price captured while the conversation is still on screen. Switching to another system to record what was just agreed is the step that doesn't happen when a shop is busy, and everything downstream depends on it happening.

What shipped
  • Orders created in place, without leaving the thread
  • Items, quantities and agreed price captured in context
  • Removes the step that gets skipped when the shop is busy
Inbox · Rosa Delgado

Photo app · @oakmoss.ceramics · 09/17/2026 10:16 AM

Search threads, ordersOakmoss CeramicsMC
RD

Rosa DelgadoLinked customer

@rosa.delgado.home · Photo app · to @oakmoss.ceramics

OK-1043 raised
Hi! Is the large speckled oat bowl back in stock?
10:02 AM#m1
It's Rosa Delgado btw, I bought the bud vases from you on Messenger in March.
10:03 AM#m2
Hi Rosa! Yes, restocked this morning. The large is $64.
Maya · 10:04 AM#m3
Could I get two of those and the 4-plate set in the same glaze?
10:06 AM#m4
Two large bowls at $64 and the 4-plate set at $96 is $224, plus $12 shipping, so $236. Shall I put that through?
Maya · 10:09 AM#m5
Yes please! Same address as last time, 418 Alder St, Apt 3B, Portland, OR 97209.
10:11 AM#m6
Any chance it arrives by Saturday? It's a gift.
10:14 AM#m7

Suggested reply · not sentDrafted from the catalog and #m2–#m7 · editable textReplying to #m7

Thanks Rosa! Your order is in: 2 × large speckled oat bowl and the 4-plate set, $236 with shipping. Orders placed before 12 PM ship the same day, so it goes out today. Delivery takes 2–4 days, so Saturday is likely.
Check before sending: the last sentence reads as a delivery promiseDiscardEditSend as Maya

Nothing reaches Rosa Delgado until a person presses Send.

What the draft was built from

The shop’s catalog and this thread, nothing else

Last six messages#m1 left out
#m2RosaIt's Rosa Delgado btw, I bought the bud vases from you on Messenger in March.
#m3MayaHi Rosa! Yes, restocked this morning. The large is $64.
#m4RosaCould I get two of those and the 4-plate set in the same glaze?
#m5MayaTwo large bowls at $64 and the 4-plate set at $96 is $224, plus $12 shipping, so $236. Shall I put that through?
#m6RosaYes please! Same address as last time, 418 Alder St, Apt 3B, Portland, OR 97209.
#m7RosaAny chance it arrives by Saturday? It's a gift.
Catalogue entries usedOakmoss Ceramics only
OAT-BWL-LLarge speckled oat bowl$64.00
OAT-PLT-44-plate set, oat glaze$96.00
POLICYOrders before 12 PM ship same day · Delivery 2–4 days
Before a person sends
Prices $64 and $96 match the catalog and #m5
Policy gives 2–4 days, not a date. Saturday isn’t promised.

Suggestions land in the composer

There is no auto-send setting to switch on. Sending is always a person’s action.

AI drafts, people send

On screen

A reply drafted from the catalog and the last six messages, waiting in the composer for a person to send, with the one sentence that reads as a delivery promise marked to check first.

Suggestions appear in the composer. Nothing reaches a customer without someone choosing to send it.

Suggested replies appear in the composer as editable text and never send themselves. The line is drawn there deliberately: a wrong price or a promised delivery date sent automatically to a customer is a commitment the business has to honor, and no accuracy figure makes that an acceptable risk. Every message that reaches a customer was chosen by a person.

What shipped
  • Suggestions land editable in the composer
  • Nothing sends without a person choosing to
  • The line drawn at commitments, whatever the accuracy
Tenancy · scoped data access

Stallbook platform · every query scoped by tenant in the data layer, below the handlers

Tenant filters in handlers: none needed Search threads, ordersPlatform adminJP

One request, traced

Session: Maya Chen · tn_oakmoss

// app/api/threads/route.ts · the handler, as written

export async function GET(req: Request) {

const db = scoped(await session(req))

return json(await db.thread.findMany({ where: { status: "open" } }))

}

No tenant filter above. The data layer adds it to every query it issues:

SELECT id, handle, last_message_at FROM thread

WHERE status = 'open'

AND tenant_id = 'tn_oakmoss' -- added by scoped(), not by the route

8 rows returned, every one tn_oakmoss

Cross-tenant probe

Another tenant’s thread
Request as tn_oakmoss

GET /api/threads/th_8K2Q

th_8K2Q belongs to tn_saltmarsh

Response404 Not found

Rows the query could see0

Filter written in the routenone

The thread exists. This tenant’s scope cannot return it, so the route has nothing to leak and nothing to remember to check.

Endpoints and where their tenant scope lives

A new route inherits the scope from the data layer
RouteAddedHandler query, as writtenTenant filter in handlerScoped by
GET /api/threadsPhase 2 · Inboxthread.findMany({ status })NoneData layer
POST /api/threads/:id/messagesPhase 2 · Inboxmessage.create({ threadId, body })NoneData layer
POST /api/webhooks/accountsPhase 2 · Inboxaccount.update({ status })NoneData layer
POST /api/customers/linkPhase 3 · Customerscustomer.link({ handle, customerId })NoneData layer
POST /api/ordersPhase 4 · Ordersorder.create({ threadId, lines })NoneData layer
PATCH /api/orders/:id/deliveryPhase 4 · Ordersdelivery.update({ orderId, status })NoneData layer
GET /api/suggestions/:threadIdPhase 5 · AIcatalogue.findMany() + thread.last(6)NoneData layer
On screen

The platform's tenancy view: a handler written with no tenant filter, the scope the data layer adds to its query, a cross-tenant probe that sees nothing, and every endpoint scoped below its handler.

Tenant scoping below the handlers

Isolation is enforced at the data layer, so a new endpoint can't forget it.

Tenant scoping is enforced in the data layer beneath the handlers, so a new route added next year can't forget it: the query simply can't return another tenant's threads. Putting the check in each endpoint would make the system's security depend on every future developer remembering, and that's not a property worth having.

What shipped
  • Isolation enforced below the handlers, in the data layer
  • A new endpoint can't forget to scope
  • Security doesn't depend on future memory

Revocation as an event

A disconnected account is known from a webhook the moment it happens, well before a message fails to send.

A revoked or disconnected account is known from the platform's webhook the moment it happens, so the inbox says so before a reply can fail in front of a waiting customer. Reconnection is prompted with the specific account named, and queued messages are held, never silently dropped.

What shipped
  • Revocation known from the webhook, ahead of any failed send
  • The specific account named in the reconnect prompt
  • Queued messages held, never dropped
Connections · 4 accounts

Connection and revocation arrive as webhooks · 09/17/2026 11:40 AM

Search threads, ordersOakmoss CeramicsMC

@oakmoss.seconds (Photo app) was disconnected at 11:32 AM

Known from the platform’s webhook the moment it happened, not from a reply failing in front of a customer. 2 replies are held.

Reconnect @oakmoss.seconds

@oakmoss.ceramics

Photo app

Connectedwebhook live

Oakmoss Ceramics

Messenger

Connectedwebhook live

@oakmoss.seconds

Photo app

Revoked · 11:32 AMsending paused

@oakmossstudio

Short video

Connectedwebhook live

Webhook events

Newest first · every connection change is an event
TimeEventAccountWhat Stallbook did
11:37:20 AMreply.queued@oakmoss.secondsJordan's reply held in its thread
11:34:51 AMreply.queued@oakmoss.secondsMaya's reply held in its thread
11:32:05 AMinbox.banner@oakmoss.secondsReconnect prompt shown, account named
11:32:04 AMaccount.revoked@oakmoss.secondsSending paused for this account
11:20:12 AMmessage.received@oakmossstudioAdded to the queue
10:58:40 AMaccount.token_refreshedMessengerStill connected
10:14:07 AMmessage.received@oakmoss.ceramicsRosa Delgado · #m7
10:08:33 AMmessage.received@oakmoss.seconds@the.lindqvists · new handle
9:02:10 AMaccount.connected@oakmossstudioThreads pulled into the inbox

Held, not dropped

Waiting on @oakmoss.seconds

To @the.lindqvistsHeld

Yes, the chipped jug is still $18. Want me to hold it for you until Friday?

Sent by Maya Chen at 11:34 AM · stays in its thread

To @june.bakesHeld

The seconds dessert bowls are back, 4 left. Same price as before.

Sent by Jordan Pike at 11:37 AM · stays in its thread

Replies written while @oakmoss.seconds is disconnected are kept in their threads until it is reconnected. Nothing is silently dropped, and the other three accounts keep working.

On screen

A revoked account known from its webhook the moment it happened: the account named in the reconnect prompt, the event log, and the replies held in their threads, none dropped.

What we built together

  • Built customer linking as one action from the thread, because a separate 'create customer' form doesn't get used

    Linking a handle to a customer is one action from inside the thread, because a separate create-customer form is a form nobody fills in when the shop is busy.

  • Kept the AI on suggestion only: it drafts, a person sends

    The AI drafts into the composer and never sends, so every message reaching a customer was chosen by a person.

  • Made orders raisable from a conversation without leaving it

    Raising an order in place was the feature the shops tested first and the one that decided whether the product was worth opening.

  • Scoped every query by tenant at the data layer, below the handlers

    Scoping was moved below the handlers early, while there were four endpoints instead of forty, since retrofitting it later means auditing every one of them.

  • Handled account connection and disconnection through webhooks so a revoked token surfaces immediately

    Account connection and revocation are known from webhooks, so a disconnected account surfaces before a reply fails in front of a waiting customer.

Outcome

Operational results after launch

1

Systems to raise an order

1

Actions to link a customer

0

AI messages sent automatically

None needed

Tenant filters in handlers

Systems to raise an order and actions to link a customer are both one. AI messages sent automatically is zero by construction: the composer requires a person. Tenant filters needed in handlers is none, because scoping is enforced a layer below them.

Client name withheld under NDA. Figures are approximate, drawn from the engagement’s own reporting.

Process

Phase by phase

  1. Phase 1: Tenancy

    Scoped At The Data Layer

    Built tenant scoping below the request handlers, so a missing filter in a new endpoint can't leak another shop's conversations. Adding a feature doesn't mean re-remembering the rule.

    • Tenant Model
    • Scoped Data Access
    • Admin Tenancy View
  2. Phase 2: The Inbox

    Accounts And Conversations

    Connected social accounts through webhooks and pulled their threads into one inbox, with revocation handled as an event the moment it happens, long before a send can fail.

    • Account Connection
    • Unified Inbox
    • Revocation Handling
  3. Phase 3: Customers

    Linked From The Thread

    Built customer auto-linking: matching a handle to an existing customer where one exists and creating one in a single action where it doesn't, from inside the conversation.

    • Auto-Link
    • Customer Record
    • Conversation History
  4. Phase 4: Orders And Delivery

    Raised Where They Are Agreed

    Built order creation from the thread and delivery tracking against the order, so the answer to 'did we ship it' lives on a record, not in someone's memory of a chat.

    • Order From Thread
    • Delivery Tracking
    • Order History
  5. Phase 5: AI Suggestions

    Drafts, Never Sends

    Added reply suggestions grounded in the shop's own catalog and the thread so far. The suggestion appears in the composer; sending is always a person's action.

    • Reply Suggestions
    • Catalogue Grounding
    • Human Send
Customers · Rosa Delgado

cus_4471 · built from conversations · 09/17/2026 11:24 AM

Search threads, ordersOakmoss CeramicsMC
RD

Rosa Delgado

Customer since 03/14/2025 · no form filled in, ever

Messenger · Rosa Delgado@rosa.delgado.home
Orders

7

all raised from threads

Lifetime value

$756.00

sum of the 7 orders

Conversations

2

on 2 linked handles

Forms filled

0

every field has a source

Orders

Each one raised in the thread that agreed it
OrderDateItemsViaAgreed inDeliveryAmount
OK-104309/17/2026Large oat bowl × 2, 4-plate setPhoto app#m5#m6Label created$236.00
OK-098807/09/2026Pour-over dripper + 2 cupsMessenger#m3#m4Delivered$112.00
OK-093403/28/2026Bud vases × 3Messenger#m5#m7Delivered$78.00
OK-085912/03/20254-plate set, moss glazeMessenger#m2#m4Delivered$96.00
OK-078809/21/2025Seconds: dessert bowls × 4Messenger#m4#m6Delivered$44.00
OK-070106/02/2025Oat glaze serving platterMessenger#m8#m9Delivered$132.00
OK-061203/14/2025Small speckled mugs × 2Messenger#m3#m5Delivered$58.00
Lifetime value · 7 orders$756.00

Conversations this record is made of

Messenger · Rosa Delgado

6 orders · since 03/14/2025

@rosa.delgado.home → @oakmoss.ceramics

1 order · linked today 10:05 AM

Delivery on OK-1043

Hangs off the order

Order raised in thread

from #m5 #m6 by Maya Chen

10:16 AM

Packed

3 items, 1 box

11:05 AM

Label created

Northway Parcel · Ground

11:20 AM

Picked up by carrier

Scheduled collection

Today, after 3 PM

Ship totraced to #m6

Rosa Delgado
418 Alder St, Apt 3B
Portland, OR 97209

#m6 · 10:11 AM · “Yes please! Same address as last time, 418 Alder St, Apt 3B, Portland, OR 97209.”

CarrierNorthway Parcel · Ground

TrackingNWP 7731 0428 5519

“Did we ship it?” is answered on this record, not from someone’s memory of a chat.

On screen

A customer record made of conversations: seven orders summing to the lifetime value, no form, and the delivery on today's order with its address traced back to the message it came from.

Ways of working

About our collaboration

Three of their own merchants sat in the reviews every two weeks, which is how the AI ended up on suggestion only. They were clear that in a channel where tone is the relationship, a wrong automated reply costs more than a slow one. Every automation proposal after that had to survive the same question.

A dedicated team for sixteen weeks, working against shops that were live on the platform from week five, well before handover. That's what settled the AI boundary: watching an operator read a suggested reply before sending it made clear how often the draft needed a small correction that only they could make.

What it settled

What we'd carry into the next one

  1. 01

    When the thread is the system of record, the product's job is to get the record out of the thread and leave the thread alone.

    The shops were never going to abandon DMs, so a product that competes with the thread loses. One that extracts the record from it is useful immediately.

  2. 02

    A separate 'create customer' form is a form nobody fills in; linking has to be one action from where the operator already is.

    A create-customer form asks an operator to leave the conversation during the busiest moment, which is exactly when they won't, so linking had to be one action.

  3. 03

    AI that sends is a liability in a channel where tone is the relationship. AI that drafts is a head start.

    In a channel where tone is the relationship, an automatically sent message is a commitment the shop has to honor. Drafting saves time; sending creates liability.

  4. 04

    Enforce tenant isolation below the handlers, or every new endpoint is a fresh chance to leak.

    Isolation enforced below the handlers means security doesn't depend on every future developer remembering, and that's not something worth betting a tenant on.

One thread, five steps, zero automatic sends

The record comes out of the conversation. The reply goes back only when a person sends it.

Step through a DM that becomes a linked customer, an order and a reply. Every field shows the message it was lifted from, and the draft waits in the composer until you press Send yourself. Switch tabs, or use the arrow keys once one is focused.

@rosa.delgado.home → @oakmoss.ceramics · 10:03 AMAI messages sent automatically:0
Hi! Is the large speckled oat bowl back in stock?
10:02 AM #m1
It's Rosa Delgado btw, I bought the bud vases from you on Messenger in March.
10:03 AM #m2

Reply to @rosa.delgado.home…

Unlinked handle

A handle, not a customer yet

Until this handle is linked, the conversation is the only place this customer exists. Stallbook has spotted a likely match in #m2, but nothing is linked until someone chooses.

  • Handle@rosa.delgado.home · Photo appon record

Hover or focus a field to light the message it was lifted from.

An illustrative thread: the shop, customer, prices, address and times are invented to show the mechanism. The steps and the zero are the product’s behavior.

Architecture

From a DM on a connected account to a record the shop owns

The thread stays where the shop sells. What the business needs to know is pulled out of it into records that belong to one tenant, and nothing goes back to a customer without a person.

  1. 01 · Source
    Connected accounts, via webhooksConnection and revocation arrive as events, so a revoked token surfaces the moment it happens, before any reply can fail to send.
  2. 02 · Inbox
    One queue across accountsEvery connected account's threads land in one inbox. While an account is disconnected, queued messages are held, not silently dropped.
  3. 03 · Record
    Customer, order, deliveryA handle links to a customer in one action, matching on prior orders and contact details. Orders are raised from the messages that agreed them; delivery hangs off the order.
  4. 04 · Data
    PostgreSQL, scoped by tenantTenant scoping is enforced in the data layer beneath the handlers, so a new endpoint can't forget it: the query can't return another shop's threads.
  5. 05 · Reply
    Composer + human sendSuggestions are grounded in the shop's catalog and the thread, and land as editable text in the composer. Sending is always a person's action.

Right tenant, right words, right door

Isolation below the handlers, drafts that wait & addresses with a source

One shop can't see another's customers

Tenant scoping is enforced once, in the data layer beneath the request handlers, instead of being remembered in each endpoint. A route added next year inherits it; the query simply can't return another tenant's threads.

No price or promise goes out unread

Suggested replies are drafted from the shop's own catalogue and the thread, and land as editable text in the composer. They never send themselves: every message that reaches a customer was chosen by a person.

The address comes from the conversation

Orders are raised from the messages that agreed them, and each captured field shows the message it was lifted from. The delivery hangs off that order, so what shipped where is on a record, not in someone's memory of a chat.

Selling through DMs, or putting AI next to a send button? Scope your build in 3 minutes.

Scope your build
Have a project?

Let's talk

Running a large platform, shaping a first MVP, or getting a product ready for a funding round? Tell us where you are. We'll shape the process around it, and stay with you after launch.