Reply in-thread
Answering a message is one call. POST /v1/emails/{id}/reply takes the id of the message you're replying to and derives everything an in-thread reply needs — the From and To addresses, the Re: subject, and the In-Reply-To / References headers — so the recipient's mail client threads it correctly.
A normal send makes you assemble the envelope yourself: pick a verified from, set the recipient, re-type the subject with a Re: prefix, and hand-write the In-Reply-To and References headers from the parent's message id. Forget any of those and the reply lands as a fresh thread. The reply endpoint does all of it from one input: the id of the message you are answering.
/v1/emails/{id}/replyThe {id} in the path is the parent message — typically the inbound message you got from a thread or the inbound_received webhook. The only required content is a body: pass html, text, or both.
curl https://api.drin.run/v1/emails/msg_in_01/reply \
-H "Authorization: Bearer $DRIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Thanks for reaching out — order #4182 ships tomorrow.",
"html": "<p>Thanks for reaching out — order #4182 ships <b>tomorrow</b>.</p>"
}'The reply is queued like any send and returns its new message id. It becomes the next outbound message in the same thread.
{
"id": "msg_out_02",
"status": "queued"
}01 What Drin sets for you
Send nothing but a body and Drin fills the rest in from the parent message:
- From defaults to the parent message's inbox address — the address it was sent to. Your reply comes from the same mailbox the conversation is already on.
- To defaults to the parent message's sender, so the reply goes back to whoever wrote in.
- Subject defaults to the parent subject with a
Re:prefix — and is not doubled when the subject already starts withRe:. - In-Reply-To and References are set from the parent's RFC 822 message id and its existing reference chain, so Gmail, Outlook, and Apple Mail collapse the reply into the original conversation instead of starting a new one.
html or text must be present. Everything else is optional — omit it and the derived default applies.02 Request body
Every field overrides the value Drin would otherwise derive. Supply only what you want to change.
text is given.html is given. Provide both for the best deliverability.Re: subject. Omit to keep the threaded default.{ email, name? }. Override the sending address — useful to hand a conversation to a different inbox (e.g. escalate from support@ to billing@). Must be an address on a domain you own.{ email, name? }[]. Override the recipients. Defaults to the parent message's sender.{ email, name? }[]. Additional copy recipients.{ filename, content, contentType? }[] with content base64-encoded.03 Overriding the defaults
The defaults cover the common case; pass any field to change it. Here a reply is escalated to a different inbox and CC'd to a supervisor — the threading headers are still derived from the parent, so it stays in the same conversation.
await drin.emails.reply("msg_in_01", {
text: "Routing this to our billing team.",
// Override any default Drin would have derived from the parent:
from: { email: "billing@yourdomain.com", name: "Acme Billing" },
cc: [{ email: "supervisor@yourdomain.com" }],
});04 The agent inbox loop
Reply is the closing move of the receive → read → respond loop. Read a thread, let an agent draft an answer from the conversation, and post it — without ever touching a mail header.
import { DrinClient } from "@drin00/sdk";
const drin = new DrinClient({ apiKey: process.env.DRIN_API_KEY });
// 1. A webhook handed you an inbound messageId. Read the full thread.
const thread = await drin.threads.get(threadId);
const last = thread.messages.at(-1)!;
// 2. Let an agent draft a reply from the conversation...
const draft = await agent.answer(thread.messages);
// 3. ...and post it. Threading is handled for you — no headers to set.
await drin.emails.reply(last.id, { text: draft });POST /v1/inbound/simulate to inject a synthetic inbound message, then call reply on the returned messageId to exercise the full loop locally. See Inbound & threads.inbound_received event so an agent can reply the moment a message lands.