OpenMail automatically groups related emails into threads using standard RFC 2822 email headers.
How it works
When an email arrives or is sent, we resolve which thread it belongs to:
- Check
In-Reply-To header - If it matches an existing message’s Message-ID, the email joins that thread.
- Check
References header - If any value matches an existing message’s Message-ID, the email joins that thread.
- No match - A new thread is created.
Outbound replies
When you send with a threadId, we automatically set:
In-Reply-To → the Message-ID of the last message in the thread
References → all Message-ID values from the thread
This ensures your reply threads correctly in the recipient’s email client (Gmail, Outlook, etc.).
We also append a quoted copy of the previous message to the body by default (the familiar On … wrote: block with >-prefixed lines). Pass includeQuote: false to send only your reply text.
When replying with threadId, subject is optional — OpenMail uses the thread’s subject (with a Re: prefix). Any subject you pass is ignored so recipient clients thread correctly.
Replying when you’re CC’d
Sometimes someone writes to one person and CCs your agent. For example, a customer replies to their contact and CCs your support inbox so you can chime in. OpenMail delivers the message to your inbox, but you may not be the person they addressed.
When you fetch a thread (GET /v1/threads/:id/messages or openmail threads get), check each inbound message:
deliveryRole: "cc" — you were copied, not the main recipient. Send your reply to headerTo (who they wrote to), not fromAddr (who sent it). OpenMail automatically CCs fromAddr on thread replies so the person who looped you in stays on the thread.
deliveryRole: "to" — your inbox was the main recipient. Reply to fromAddr as usual.
To copy additional people on your reply, pass cc on send (API) or --cc on the CLI (repeatable). Explicit cc values are merged with the auto-CC.
| Field | What it means |
|---|
fromAddr | Who sent the email |
toAddr | Your OpenMail inbox address |
headerTo | Who the sender addressed (comma-separated if multiple) |
deliveryRole | to if you were the main recipient, cc if you were copied |
cc | Other people copied on the email |
Thread context
Fetch all messages in a thread via GET /v1/threads/:id/messages. Messages are returned in chronological order, giving your agent the full conversation history to inform its next action.
Read/unread tracking
Every thread has an is_read flag that tracks whether your agent has processed it. This prevents agents from reprocessing the same emails.
How it works
- New inbound threads start as unread (
is_read: false)
- Sending a reply automatically marks the thread as read
- Your agent explicitly marks threads as read via
PATCH /v1/threads/:id
Recommended flow
Poll for unread threads
Fetch only threads your agent hasn’t processed yet.GET /v1/inboxes/:id/threads?is_read=false
Process the thread
Fetch messages, run your agent logic, send a reply if needed.GET /v1/threads/:id/messages
Mark as read
After successful processing, mark the thread as read so it won’t appear on the next poll.PATCH /v1/threads/:id
{ "is_read": true }
If your agent sends a reply, this happens automatically.
If your agent uses webhooks or WebSockets, the same pattern applies — mark the thread as read after handling the event. Then use ?is_read=false as a safety net to catch any events missed during downtime.