Rookery
Developer docsSend from your app, reply in Rookery
Guides

Send from your app, reply in Rookery

Your app keeps sending its own email. Customers' replies land in Rookery as conversations, linked to your order or account, and your app hears back.

Updated View as Markdown

Your app sends its own email: receipts, shipping updates, password resets, alerts. Keep sending it through Resend, Postmark, SES or whatever you use today. Rookery takes over at Reply: the customer's answer becomes a conversation in the right team inbox, already linked to the record it's about, with the email your app sent above it. Your team answers from Rookery, and your app hears about it through webhooks.

Rookery is the bridge, not the sender. It doesn't send your app's mail, and it never writes to your app's recipients on its own.

1. Change one line: the reply address

Set the reply address on the email your app sends to a Rookery inbox, with your record's id after a +. Plus tags survive every mail app's Reply, which custom headers don't.

// Resend
await resend.emails.send({
  from: "Acme <orders@acme.com>",
  to: customer.email,
  replyTo: `support+${order.id}@acme.com`, // the reference rides along
  subject: `Your order ${order.id} has shipped`,
  html,
});

The same line for other services:

ServiceThe field
ResendreplyTo: "support+order_8841@acme.com" (reply_to over HTTP)
PostmarkReplyTo: "support+order_8841@acme.com"
Amazon SESReplyToAddresses: ["support+order_8841@acme.com"]
SendGridreplyTo: "support+order_8841@acme.com" (reply_to over HTTP)
Mailgun"h:Reply-To": "support+order_8841@acme.com"
SMTP (Nodemailer and others)replyTo: "support+order_8841@acme.com" (the Reply-To header)

When the customer replies, the conversation shows Ref order_8841. A tag is a reference when it's letters, digits, _ and -, with at least one digit, up to 40 characters, so support+shop@ stays an ordinary plus address. It comes back lowercased, as email addresses are.

The inbox must be one Rookery receives for: an address on your domain that reaches Rookery, or your Rookery address.

2. Tell Rookery what your app sent (optional)

With only the reply address, Rookery knows the reference but not the email it answers. Tell it what you sent and the reply threads exactly, by its headers, and opens with What your app sent. Two ways:

Connect Resend: no code

In Settings → Developers → Connections, an owner or admin pastes a Resend API key with Full access. Rookery adds a webhook for email.sent to your Resend account and notes each email your app sends. Tag an email reference in Resend to give it a reference without a plus tag. When a customer replies, Rookery reads that one email's words from Resend. See Get replies to your app's email.

Or call record_sent

From any service, after it accepts the email, call Record what your app sent with a key that has the record scope (Record app mail):

curl https://app.userookery.com/api/v1/app-mail \
  -H "Authorization: Bearer $ROOKERY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messageId": "<0102019a3c4e-order-8841@mail.acme.com>",
    "reference": "order_8841",
    "to": ["lee@example.com"],
    "subject": "Your order 8841 has shipped"
  }'

Use the Message-ID header the email went out with, as your sending service reports it (Resend's API and webhook call it message_id). Not sure which it is? Send one to yourself and look at the original. Without it, the plus tag still carries the reference. Add text or html to keep its words too. Recording the same messageId again is safe: it updates the outline until someone replies, and after that only fills in what was missing. MCP clients get the same tool, record_sent.

Only a reply from one of the to addresses, or from someone already on that conversation, and signed by the sender's own domain (DMARC, as mail from Gmail, Outlook and most providers is), starts or joins a conversation with this email. Anyone else who names its Message-ID gets a conversation of their own, without your reference. Use Message-IDs nobody can guess: the ones Resend, Postmark and SES give already are. Don't build one from the order id (<receipt-8841@…>): the next customer's is easy to guess.

3. Hear back in your app

The webhooks you already have carry your reference:

  • client.replied: a customer wrote back, including the first reply to your app's email. In an inbox that screens new senders, a first-time sender's reply comes once someone on your team lets them in.
  • message.sent: your team answered.
  • turn.changed: whose turn it is now: ours, theirs or done.
{
  "type": "client.replied",
  "data": {
    "conversation_id": "cnv_zys19vqj63sb1z4v",
    "reference": "order_8841",
    "summary": "Lee Park replied"
  }
}

Check the sender

A reference says which address someone wrote to, not who wrote. Anyone can write to support+order_8841@. Before your app acts on a client.replied (attaching it to order 8841, or answering with the order's details), read the conversation and check that its contact is the customer on that order. If it isn't, leave it to your team.

To show "2 replies in support" on the order page, list conversations by reference, in every status:

curl "https://app.userookery.com/api/v1/conversations?reference=order_8841&view=all" \
  -H "Authorization: Bearer $ROOKERY_KEY"

Read a conversation returns reference and appSent, the email your app sent.

The limits

Rookery doesRookery doesn't
Receive replies to your app's email and file them in the right inboxSend your app's mail, newsletters or bulk mail
Keep what your app sent as context: who, when, the subject; its words once someone repliesShow your app's mail in anyone's inbox before a reply, or count it as email sent from Rookery
Tell your app about replies and status, with your referenceWrite to your app's recipients on its own
  • 30 days. An email nobody replies to is forgotten after 30 days. Once someone replies, it stays with the conversation.
  • 50,000 per workspace. That's how many emails without a reply a workspace keeps. Past that, the oldest go first.
  • Words only after a reply. With Connect Resend, Rookery reads an email's words from Resend only when someone it went to replies to it. A body you send with record_sent is kept with the outline, and goes with it.
  • Plans. Like the API and webhooks, it's on Pro and Team.
  • The sandbox. A test key's record_sent goes to the sandbox: only replies simulated there can answer it.