Skip to content
( SUPPORT )

Replies land back in your app.

A customer answers a receipt or a notification and the reply threads into the same conversation your app already knows about, so your support tooling picks up the history instead of starting cold.

( ONE THREAD, NOT THREE MESSAGES )

A reply lands on the thread it answers.

Outbound sends, replies, and the next outbound message all carry the same conversation id. A signed event tells your app when a new reply arrives, and the conversation object is what you fetch to see the whole exchange.

-> outbound  "Your order shipped"     msg_01hv8kq2
<- reply     "Which day exactly?"     msg_01hx2p7d
-> outbound  "Thursday, by noon."     msg_01hx9r4k

conversation {
  id: "conv_01hx3f",
  contact: "ada@example.com",
  messages: 3
}
POST /webhooks/samva
X-Webhook-Event: message.received

{ event: "message.received",
  data: { direction: "inbound",
    conversationId: "conv_01hx3f" } }
( THE PROBLEM )

A reply shouldn't mean starting from zero.

Thread context breaks between email and your helpdesk.

A customer's reply arrives as a fresh message with no link to the receipt or notification it answers, so your team pieces the history back together by hand.

You end up writing an inbound parser.

Raw MIME, multipart bodies, and attachment extraction turn into a small project of their own before a single reply reaches your app.

Reply chains break.

A forward or a bounce drops out of the thread it belongs to, and your team loses the conversation exactly when a customer is already frustrated.

( HOW IT WORKS )

Parsed, threaded, delivered.

#01PARSE

Inbound is parsed for you.

MX records on your domain route incoming mail to Samva. Each raw MIME message is parsed into its headers, body, and attachments, no parser to write or maintain.

MX support.example.com -> samva

parsed {
  from: "ada@example.com",
  subject: "Re: Your order shipped",
  hasAttachments: false
}
#02THREAD

Replies group into one conversation.

Replies, forwards, and bounces carry a conversationId that ties them to the send they answer. Threading is the data model, not a convention you maintain in a column.

-> outbound  "Your order shipped"
<- reply     "Which day exactly?"

conversation.id: "conv_01hx3f"
# one id, every reply, forward, and bounce
#03DELIVER

Fetch the thread by id.

A signed message.received event lands at your endpoint with the conversation id. Fetch the full thread from the conversations API whenever your app needs it.

GET /v1/conversations/conv_01hx3f

{ id: "conv_01hx3f",
  contact: "ada@example.com",
  messages: 3 }
( KEEP READING )

Wire up inbound from here.

( FAQ )

Questions from support and CX leads.

How does threading work?

Every inbound message carries a conversationId that ties it to the outbound send it answers. Replies, forwards, and bounces all land on the same conversation, so you fetch one thread instead of reconciling separate messages.

What about attachments on inbound?

Each inbound message is parsed into its headers, body, and attachments. The message.received event flags whether the email carried attachments, and you fetch the files themselves through the attachments API.

How do I reply from my app?

Call messages.send() and address the contact directly, threading the reply into the existing conversation. The response reaches the customer from your app instead of a shared inbox someone has to check.

How does routing work across domains?

Each domain you verify can have inbound receiving turned on, and you can register more than one webhook endpoint. Subscribe each endpoint to message.received so inbound for a support domain routes to a different service than inbound for your product domain.

Get started

Let the next reply land in your app.

Verify a domain, turn on receiving, and register your endpoint.