# Turn replies to your emails into support conversations

Receipts, order updates and account notices get answered. Add one field to the emails your app already sends, and every reply opens a conversation in your support channel, linked to the email it answers.

Using an AI coding agent? Give it this:

```text
Route replies to this app's emails into our Rasket support inbox.
Read the Markdown of Rasket's guide first: /docs/inbox-api/replies.md on the Rasket website.
1. Find every place this app calls POST /emails (or rasket.emails.send) for an email a customer might answer: receipts, order updates, account notices.
2. Add inbox: "support@yourdomain.com" to each of those sends. Remove reply_to from them: the two cannot be sent together.
3. Leave password resets and sign-in links alone; nobody should answer those.
4. Do not change anything else about the sends. Show me the diff.
```

## 1. Add inbox to a send

Name a shared channel by its full address or its ID. Any key that may send can do this, because it routes mail in and reads nothing: no Inbox access is needed.

Send an email whose replies go to the inbox:

```sh
curl -X POST "https://api.rasket.com/emails" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: receipt-8412-october" \
  -d '{
  "from": "Lumen <billing@lumen.app>",
  "to": ["priya@harborcoffee.co"],
  "subject": "Your Lumen receipt — October",
  "html": "<p>Thanks for your payment of $49.</p>",
  "inbox": "support@lumen.app"
}'
```

```ts
const response = await fetch("https://api.rasket.com/emails", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
    "Content-Type": "application/json",
    "Idempotency-Key": "receipt-8412-october",
  },
  body: JSON.stringify({
    from: "Lumen <billing@lumen.app>",
    to: ["priya@harborcoffee.co"],
    subject: "Your Lumen receipt — October",
    html: "<p>Thanks for your payment of $49.</p>",
    inbox: "support@lumen.app"
  }),
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://api.rasket.com/emails",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
        "Idempotency-Key": "receipt-8412-october",
    },
    json={
    "from": "Lumen <billing@lumen.app>",
    "to": ["priya@harborcoffee.co"],
    "subject": "Your Lumen receipt — October",
    "html": "<p>Thanks for your payment of $49.</p>",
    "inbox": "support@lumen.app"
  },
)

id = response.json()["id"]
```

- The email goes out with `Reply-To: support@lumen.app`. Don't send `reply_to` as well.
- It works the same on every email in a batch.
- A restricted channel needs a key limited to it. Personal mailboxes can't be named.

## 2. The customer replies

The reply opens a conversation in that channel, or joins the one already open for that email. Your team sees which email it answers. In the dashboard, the sent email shows **Replies go to** with the channel, and then **Customer replied** with a link to the conversation.

## 3. Find the conversation from your app

Read the email back. Once the customer has replied, it carries the conversation's ID; until then `conversation_id` is null.

Retrieve an email:

```sh
curl -X GET "https://api.rasket.com/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

id = response.json()["id"]
```

What the email adds:

```text
{
  "inbox": {
    "channel_id": "0192f6a8-3c1e-7a40-9b2d-5e8f1a6c4d20",
    "conversation_id": "0192f8e1-7a40-7c55-b1e3-0f0a3b6d2e91"
  }
}
```

Or list the open conversations in the channel with a key that has Inbox access **Read**:

List conversations:

```sh
curl -X GET "https://api.rasket.com/inbox/conversations?status=open&limit=20" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/inbox/conversations?status=open&limit=20", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/inbox/conversations?status=open&limit=20",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

print(response.json())
```

## 4. Hear about it

Turn on `inbox.conversation.created` and `inbox.message.received` on a webhook endpoint. Both name the conversation and carry no message text. They are on [Events](https://www.rasket.com/docs/events).

> A reply counts as received mail, which is free. Your team's answer from the Inbox is a send, like any other email.

## If something goes wrong

| Answer | What to do |
| --- | --- |
| `422 validation_error` | `inbox` and `reply_to` were both sent. Keep `inbox`: it sets `Reply-To` for you. |
| `422` | The channel is unknown, your project has no Inbox yet, or the channel is restricted and the key is not limited to it. Write the full address, such as support@lumen.app — a bare support is ambiguous across your domains. |
| The reply did not arrive | Open the sent email in the dashboard. If it has no Replies go to line, the send did not name the channel; read it back with GET /emails/{email_id} to check. |
