> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect email

> Give your agent an inbox that Lua generates or forward your own support address to it, then send threaded email from code

After this guide, email sent to an address of your choice is answered by your agent in the same thread, and the agent can send email on its own with `Channels.email.send`. Two modes exist: a generated inbox, where Lua creates the address from a display name you choose, and an existing address, where you forward mail from your own mailbox to an address Lua gives you. Both are available in the admin dashboard and in `lua channels`.

*Verified against lua-cli 3.33.0.*

**Before you begin**

* A display name for the agent's mail, shown in the `From` header.
* For an existing address: access to that mailbox's forwarding settings, or to your mail provider's routing rules.
* An agent with a promoted production version; see [Release an agent](/ship/releasing).

<Steps>
  <Step title="Link the address">
    <Tabs>
      <Tab title="Dashboard">
        In the [admin dashboard](/get-started/for-operators) open **Agents**, select the agent, select **+** in the **Channels** section of its **Overview** tab, and choose **Email** in **Connect a channel**. Under **Connection type** pick **Generate new email** or **Use existing email**, enter the **Display name** (and, for an existing address, the **Email address**), and select **Connect**. **Email Channel Created** then shows the new address; **Email Channel Connected** shows the address to forward your mail to, with forwarding steps for Gmail and Outlook.
      </Tab>

      <Tab title="CLI">
        Run `lua channels`, choose **Link new channel**, then **Email**, pick **Generate new inbox** or **Use existing email**, and enter the display name (and, for existing, the sender address). The CLI prints the generated address, or the forwarding address with the forwarding instructions.

        ```bash theme={null}
        lua channels
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Provider console: forward your mail (existing address only)">
    In your mail provider, forward everything that arrives at your address to the forwarding address Lua printed. Gmail and Google Workspace send a confirmation to the forwarding address before they activate it; the agent receives that confirmation, so complete the step from the provider's side. Until forwarding is active, nothing reaches the agent.
  </Step>

  <Step title="Verify">
    Send an email to the connected address with the subject `Test` and a question in the body. The reply arrives in the same thread with subject `Re: Test`. The inbound turn is in the agent's logs.

    ```bash theme={null}
    lua logs --type user_message
    ```
  </Step>
</Steps>

## Channel behavior

| Capability | Supported |
| - | - |
| Inbound text and attachments | Yes; attachments reach the agent as file parts |
| Inbound embedded images | Forwarded when referenced from the HTML body, at least 1 KB, up to 10 per email; images hosted at a URL are not fetched |
| Reply threading | `Re:` is added to the subject, and `In-Reply-To` and `References` are set to the inbound message |
| Email metadata in code | `Lua.request.webhook.payload` on the forwarding channel: `messageId`, `inReplyTo`, `references`, `subject`, `from`, `to`, `cc`, `bcc`, `replyTo`, `date`, `headerLines` |
| Response components, forwarding channel | `list-item`, `images`, `links`, `payment` rendered in the branded template; `actions` and `reaction` removed; `horizontal-list-item`, `documents`, `flow`, `navigate` left as text |
| Response components, generated inbox | `images` and `documents` sent as attachments; other blocks dropped (the surrounding text is sent) |
| Proactive sends | `Channels.email.send` to any address, with no window |
| Human takeover | Off by default; a colleague's reply pauses the agent on that conversation, see [Human takeover](#human-takeover) |
| Settings | Changed in the admin dashboard or the admin API, see [Settings](#settings) |

`Lua.request.webhook.payload` is typed `unknown`; narrow it before reading fields. On a generated inbox the payload is the provider's message object instead, with snake\_case fields such as `message_id`, `in_reply_to`, `references`, `subject`, `from`, `to`, `cc`, `text`, and `attachments`.

### Send email from code

`Channels.email.send` takes a recipient as `email` (any address) or `userId` (resolved from the end user's history), an optional `subject`, and exactly one body: `text` as plain text, `html` sent as-is, or `richBody`, Markdown and `:::` components rendered into the same branded template the agent's replies use. `cc`, `bcc`, and `attachments` (fetched from a URL server-side) are optional. To keep a follow-up in an existing thread, pass the inbound email's `messageId` as `inReplyTo` and its chain in `references`; these are honored on the forwarding channel.

```ts src/tools/SendReceiptTool.ts theme={null}
import { Channels } from 'lua-cli';

export async function sendReceipt(to: string, inboundMessageId: string) {
  return Channels.email.send({
    to: { email: to },
    subject: 'Your receipt',
    richBody: 'Thanks for your order. Your receipt is attached.',
    attachments: [
      { filename: 'receipt.pdf', contentType: 'application/pdf', url: 'https://example.com/receipts/inv_123.pdf' },
    ],
    inReplyTo: inboundMessageId,
    references: [inboundMessageId],
  });
}
```

The result's `messageId` is the provider's message ID and `status` is `accepted` when the call returns; see the [`Channels` reference](/reference/sdk/channels).

## Settings

Open **Agents**, select the agent, and in the **Channels** section of its **Overview** tab select the email channel. The panel shows the channel's status first, then every setting with its default. A toggle saves when you flip it and can be undone from the confirmation; a text or list field saves when you leave it. **Change history** lists who changed which setting, and when.

The status shows the sending address and, for an existing address, whether Amazon SES has verified it and the forwarding address to send your mail to. It also shows when the last email arrived and, once your mail rule copies colleagues' replies to Lua (see [Human takeover](#human-takeover)), when the last copy arrived. These times are recorded from 30 September 2026; mail that arrived earlier isn't counted.

| Setting | Key | What it does | Default | Channel |
| - | - | - | - | - |
| Sender name | `email.name` | The name customers see next to your address | The display name you linked the address with | Existing address |
| Customers reply to the Lua inbox | `email.replyToInbox` | Sets `Reply-To` to the forwarding address, so replies reach the agent without a forwarding rule and skip your mailbox | Off | Existing address |
| Copy every agent email to | `email.bcc` | Sends a hidden copy of every email the agent sends to up to 10 addresses, for example a shared mailbox. The forwarding address is refused | None | Both |
| One conversation per email thread | `email.threadScoping` | Makes each email thread its own conversation; off, every email from a person shares one. See [Thread scoping](#thread-scoping) | On for channels linked from 29 September 2026; off for channels linked earlier | Both |
| Pause the agent when a colleague replies | `takeover.enabled` | Turns on [human takeover](#human-takeover) | Off | Existing address |
| Pause length | `takeover.idleReleaseMinutes` | Minutes after a colleague's last reply before the agent answers again, 0 to 129 600. `0` keeps the conversation paused until someone hands it back | 10 080 (7 days) | Existing address |
| Staff email domains | `takeover.staffDomains` | Up to 20 domains whose senders count as colleagues | The domain of the channel's address | Existing address |

A generated inbox has only the two settings marked **Both**.

To change settings from code, send only the keys you change. `null` resets a setting to its default; a setting without a default, such as the sender name, can't be reset. The response is the channel's full status and settings, each with its `value` and its `source`: `stored`, `default`, or `derived`. `<channel>` is the channel's `id` or its address.

```bash theme={null}
curl -X PATCH "https://api.heylua.ai/admin/agents/<agentId>/channels/<channel>/settings" \
  -H "Authorization: Bearer <api key>" -H "Content-Type: application/json" \
  -d '{"changes":{"takeover.enabled":true,"takeover.staffDomains":["example.com"],"email.bcc":["team@example.com"]}}'
```

An invalid value answers `400` with `errors`, keyed by setting, and changes nothing. `GET` on the same path reads the settings, and `GET …/settings/history` lists past changes, newest first. Changing settings needs the Builder role on the agent; Operators can read them.

## Human takeover

With human takeover on, a colleague who replies to a customer takes that conversation over. While the conversation is held, the agent stores the customer's messages but doesn't reply and runs nothing, not even a model call. Takeover is off by default and set per channel; on email it works on a channel linked to an existing address.

The colleague's message is saved in the conversation, marked as written by a colleague and already sent to the customer, so the agent sees it on later turns. The admin dashboard shows it as a colleague's message. A message an operator sends to the customer from the admin dashboard also holds the conversation, except when thread scoping is on.

Turn it on, and set how long the pause lasts and which domains count as colleagues, in [Settings](#settings). On other channels the pause length defaults to 24 hours on WhatsApp, Instagram, Messenger, and SMS, 30 minutes on web chat and Front, and 24 hours anywhere else.

### How a colleague's reply is detected

**Without setup.** When a customer replies to an email the agent never sent or received, Lua reads the part the customer's mail quoted. If it shows the email came from one of `staffDomains`, or shows only a name, the conversation is held and the quoted text is saved as the colleague's message. If the quoted author is someone else, such as another person on CC, nothing is held. In this mode Lua learns of the colleague's reply only when the customer answers it.

**With a mail rule.** One rule in your own mail system copies every email sent from the channel's address to Lua. The colleague's full reply then reaches Lua within seconds, and the conversation is held before the customer answers.

<Steps>
  <Step title="Provider console: create a rule for mail sent from the channel's address">
    In Exchange or Microsoft 365, create a transport rule; in Google Workspace, create a routing rule. Make it match mail sent from the address the channel answers for, for example `support@example.com`.&#x20;
  </Step>

  <Step title="Provider console: Bcc the forwarding address">
    Set the rule's action to add a Bcc recipient: the forwarding address Lua gave you when you linked the channel.
  </Step>

  <Step title="Verify">
    With **Pause the agent when a colleague replies** on, reply from the channel's address to a customer who already has a conversation with the agent. Within seconds the admin dashboard shows your reply in that conversation as a colleague's message, and the customer's next email is stored without an answer from the agent.
  </Step>
</Steps>

Lua never replies to mail that comes from the channel's own address, so these copies are never answered. A colleague's message is saved only for recipients who already have a conversation with the agent. Other recipients, such as a contractor copied on the reply, are ignored, and no user is created for them.

### Thread scoping

With `email.threadScoping` off, an email channel keeps one conversation per sender, so a hold covers everything that customer sends. With it on, each email thread is its own agent conversation, and a colleague's reply holds only that thread. Change it in [Settings](#settings).

<Warning>
  Changing thread scoping starts fresh conversations for threads that are already open. The earlier history isn't carried into them.
</Warning>

### Hand the conversation back

The hold ends on its own once `idleReleaseMinutes` pass without a colleague reply; every colleague reply starts the wait again. To end it sooner, open the conversation in the admin dashboard and select **Hand back**. The agent then answers again.

### Sending while a conversation is held

`Channels.send`, `Channels.whatsapp.sendTemplate`, `Channels.whatsapp.sendReaction`, `Channels.email.send`, and the [REST send routes](/reference/rest/channels) refuse a proactive send to a held conversation with HTTP `409` and the code `CONVERSATION_HELD`, whether the end user is addressed by user ID or by phone number. Pass `options: { ignoreHold: true }` to send anyway; the option is in lua-cli's types from the next lua-cli release. A message sent with `user.send()` while the conversation is held is not delivered, and has no override: hand the conversation back first. For email, a send with `inReplyTo` is checked against that thread's conversation, and a send without thread context against the customer's default conversation.

## Limits

* Generated address: the local part is the display name lowercased, with spaces and underscores turned into hyphens, other characters except letters, digits, `.` and `-` removed, and cut to 30 characters; a four-character suffix is added if the name is taken. `Support Team` becomes `support-team@…`.&#x20;
* Outbound attachments: the raw bytes of all attachments on one email total at most 28 MB.
* Inbound embedded images: at least 1 KB each, at most 10 per email.
* Linking an existing address a second time doesn't create a second channel: the channel already registered for that address is updated, including the agent it belongs to, and the same forwarding address is returned. Each generated-inbox link creates a new inbox.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Nothing arrives on an existing address">
    Forwarding isn't active. Check the provider's forwarding rule and, for Gmail or Workspace, that the forwarding address was confirmed. Test by emailing your own address, not the forwarding address.
  </Accordion>

  <Accordion title="email send requires a text, html, or richBody body">
    `Channels.email.send` was called with none of the three bodies. Pass exactly one.
  </Accordion>

  <Accordion title="Attachments exceed the 28MB limit">
    The attachments on one email add up to more than 28 MB of raw data. Split them across emails or link to the file instead.
  </Accordion>
</AccordionGroup>

## Next steps

<Columns cols={2}>
  <Card title="Send proactive messages" href="/build/send-proactive-messages">Receipts, reports, and reminders from jobs and webhooks.</Card>
  <Card title="Channels reference" href="/reference/sdk/channels">Every field of `Channels.email.send` and the delivery status model.</Card>
  <Card title="Response formatting" href="/channels/formatting/overview">What each component becomes in email.</Card>
  <Card title="About channels" href="/concepts/channels">The inbound and outbound channel vocabularies.</Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.