Skip to content
agents

Design email for humans and AI 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.

·

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 gives your agent an address it can answer from, on that same thread.

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.

<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 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

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:

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

And the text part:

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

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.

<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 to read the HTML before it goes out. The templates page is that workflow.

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 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 is the full path, including the signature check. From the verified event:

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

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:

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 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 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.

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 when you are ready to add that path. The inbound guide is the webhook reply. Mailboxes is the address the agent owns.

Keep reading

Related Resources

Send

Ship your first email today.

Transactional and product email through one typed API, with signed events, conversation threading, and deliverability handled.