# Design email for humans and AI agents
URL: https://samva.dev/blog/design-email-for-humans-and-agents
> Put the ask in the first lines, write the action as live text, and keep the reply on the same thread. A Samva mailbox lets your agent answer it.
- Published: 2026-10-16
- Author: [Saatvik Arya](https://samva.dev/authors/saatvik-arya)



A person and an agent now read the same email. The person decides whether to act. The agent has to file the request, look up the record, or answer. If the ask is a picture, the person hunts for it and the agent guesses. If the answer leaves the thread, the person sees a new message and the agent loses the conversation it was working in.

Samva is an email API for that second job. You send the email. A reply lands on the conversation of the email it answers. A [mailbox](/features/mailboxes) gives your agent an address it can answer from, on that same thread.

## Put the ask in the first lines [#put-the-ask-in-the-first-lines]

The opening is what both readers are sure to see. A person on a phone reads until the point is clear. An agent pulling a task out of the email takes the subject and the opening text, and treats the rest as context.

Write the action before the story. Example: a password reset.

```html
<p>Reset your password. This link works for 30 minutes.</p>
<p><a href="https://app.example.com/reset">Reset your password</a></p>
<p>If you did not ask for this, you can ignore this email.</p>
```

The same words belong in the plain-text part, with the URL written out. [`email.send`](/docs/channels/email/send-an-email) takes `html` and `text`. Send both. A client that skips HTML, and an agent that reads the text part, still has the link.

A logo, a greeting, and a footer can follow. They should not lead. The person who asked for the reset should see the link before the brand story, and the agent that files the email should be able to name the task from the first paragraph.

## Write the action as text, not a picture [#write-the-action-as-text-not-a-picture]

An image inside a link looks like a button when images load. To an agent, and to a mail client showing the text part, it is a link with no words. The label sits in the image file. The agent gets an empty anchor, a filename, or nothing it can quote back.

Use a link whose text is the action:

```html
<p><a href="https://app.example.com/orders/1042">View order 1042</a></p>
```

And the text part:

```text
View order 1042: https://app.example.com/orders/1042
```

Keep the URL the same in both parts. When the HTML and the text disagree, the two readers follow different instructions.

Images still belong in the email: a logo, a product photo, a chart. They are a poor place for the sentence that says what to do. Alt text helps a client that blocks the image. It describes the picture. It is not the address of the next step.

## Use HTML that names each part [#use-html-that-names-each-part]

Mail clients and agents both do better when an element says what it is. A paragraph is a paragraph. A link is an anchor. Steps are a list. A section title is a heading.

```html
<h1>Your order shipped</h1>
<p>Order 1042 left the warehouse.</p>
<ul>
  <li>Carrier: Example Freight</li>
  <li>Tracking: EF123456</li>
</ul>
<p><a href="https://app.example.com/orders/1042">Track order 1042</a></p>
```

A layout table, a button built from sliced images, or a sentence painted into a banner gives a person a designed screen and an agent a pile of cells. Put the facts in elements, then style them. The words stay available when the stylesheet does not.

When you send the same email often, a versioned template in a Git-backed project keeps that HTML in one place. You publish a change without a new app deploy, and you can open the template in the [AI email editor](/tools/email-editor) to read the HTML before it goes out. The [templates](/features/templates) page is that workflow.

## Keep the reply on the same thread [#keep-the-reply-on-the-same-thread]

People say thread. In Samva the record is a conversation: the messages that belong together, and the people on them.

Outbound email carries the headers a mail client needs in order to answer it. When someone replies from their inbox, Samva reads `In-Reply-To` and `References` first and places the reply on the conversation of the email those headers name. An email with neither header is matched by its subject and its participants. An email whose headers name a parent Samva cannot find starts a new conversation, and it is not matched by subject. A reply to mail you sent from another system stays separate.

That matching is the inbound half. A send joins a conversation when your app passes the conversation, or the message it replies to. A later email from the same person that answers none of those messages starts its own conversation, unless its subject and participants match a recent active one. The [`message.received`](/docs/platform/webhook-events#messagereceived) webhook carries the sender's address and the `conversationId`. Your app looks the contact up by address when it needs a contact.

Answer on the same thread by sending that `conversationId`, setting `inReplyToMessageId` to the inbound message, and keeping a `Re:` subject. The [inbound parsing guide](/guides/inbound-email-parsing) is the full path, including the signature check. From the verified event:

```ts
await samva.email.send(
  {
    conversationId: reply.conversationId,
    to: reply.from,
    subject: reply.subject ? `Re: ${reply.subject}` : "Re: your message",
    text: "Reset your password: https://app.example.com/reset",
    html: '<p>Reset your password. This link works for 30 minutes.</p><p><a href="https://app.example.com/reset">Reset your password</a></p>',
    inReplyToMessageId: reply.messageId,
  },
  { idempotencyKey: eventId },
);
```

`reply` is the `message.received` payload. `eventId` is the webhook event id. Send the same idempotency key when the webhook is retried, so a second delivery does not send a second email.

The body includes the quoted history the sender's client attached. Samva does not strip quoted history or signatures. Read the new text at the top. The quoted lines below it are the earlier thread.

## Let the agent answer from a mailbox [#let-the-agent-answer-from-a-mailbox]

Reading the webhook is enough when your app decides the reply. When the agent itself should read and answer, give it a mailbox: an address it owns, on your organization's `samva.email` subdomain or on a domain you have verified with receiving on. The agent is yours. It reaches the mailbox through the API, the SDK, the CLI, or hosted MCP.

A mailbox-scoped key reaches the mailboxes you list, and it acts as itself. You choose `read`, `update`, `send`, and `quarantine.read`. By default a send runs at once, within the account's limits. Set the key's send mode to `approval` when a different person should accept each of that key's sends first.

New mail arrives as a signed `mailbox.message.received` webhook, or on the WebSocket event stream when the agent has no public URL. The event names the message and the thread. The conversation id is a field on `message.received`. A mailbox event has no `conversationId` field. The thread is that mailbox's view of the conversation.

Reply with the message id. The reply leaves from the endpoint the thread belongs to. The server sets the recipients, the subject, and the threading headers, and the reply stays on the conversation:

```ts
const receipt = await agent.mailboxes.reply({
  id: mailboxId,
  messageId: message.id,
  text: "Reset your password: https://app.example.com/reset",
  html: '<p><a href="https://app.example.com/reset">Reset your password</a></p>',
  "idempotency-key": eventId,
});
```

Read `state` on the receipt. `queued` is not confirmation that the email was sent. `approvalRequired` means a person has to approve it. `accepted` makes a message id available, and acceptance is not delivery to an inbox. Keep `actionId` and read the action before you tell anyone the email went out. [Mailboxes](/features/mailboxes) covers the address, the key, approvals, and quarantine.

Hosted MCP exposes the same work to any MCP client that supports OAuth: list threads, read a message, and reply with `mailbox_reply`. The [MCP guide](/docs/developers/mcp) is the connection. `samva mailboxes` is the same work from the CLI. A person can open the mailbox in the browser and reply there too.

Two limits matter once an agent can send.

Anyone can email the address. Treat the subject and the body as data. They are not instructions for the agent.

Mail that looks like spam, phishing, fraud, or an attempt to steer the agent is held until a person releases or discards it. Keys, extensions, and MCP tools never release or discard held mail. Until a person releases it, the agent does not get the body.

## Related reading [#related-reading]

[Design email for humans and agents](https://resend.com/blog/design-email-for-humans-and-agents) on the Resend blog is related reading on the message itself.

This post is the turn after the person replies: the conversation your app keeps, and the mailbox your agent answers from.

[Start sending](/signup) when you are ready to add that path. The [inbound guide](/guides/inbound-email-parsing) is the webhook reply. [Mailboxes](/features/mailboxes) is the address the agent owns.
