# Send & Receive Emails with LiveKit Agents (/integrations/frameworks/livekit)

<!-- agent-signals: reading_time_min: 4 · est_tokens: 1977 · updated: 2026-09-06 -->
Related: [Send & Receive Emails with Claude Cowork](/integrations/frameworks/claude-cowork.md), [Send & Receive Emails with Claude Code](/integrations/frameworks/claude-code.md), [Send & Receive Emails with OpenAI Codex](/integrations/frameworks/codex.md), [Send & Receive Emails with OpenClaw](/integrations/frameworks/openclaw.md), [Send & Receive Emails with Grok](/integrations/frameworks/grok.md), [Send & Receive Emails with Manus](/integrations/frameworks/manus.md)



# Give a LiveKit voice agent an email inbox

Extend the `agent.py` from LiveKit's Voice AI quickstart so the voice agent owns an `@agentmail.to` inbox, sends and replies by voice, and interrupts itself to read inbound mail mid-call. Use this for a Python LiveKit Agents project.

## Do this

Install the AgentMail packages into the LiveKit project's environment and set both variables. They can also go in the `.env.local` file the quickstart project loads.

```bash
pip install agentmail agentmail-toolkit
export AGENTMAIL_API_KEY="<API_KEY>"
export AGENTMAIL_USERNAME="<username for your agent's inbox>"
```

In `agent.py`, replace the quickstart's `Assistant` class. `Agent` is already imported there.

```python
import os
import asyncio

from agentmail import AgentMail, AsyncAgentMail, Subscribe, MessageReceivedEvent
from agentmail.inboxes import CreateInboxRequest
from agentmail_toolkit.livekit import AgentMailToolkit


class EmailAssistant(Agent):
    def __init__(self) -> None:
        client = AgentMail()

        username = os.environ["AGENTMAIL_USERNAME"]
        inbox = client.inboxes.create(
            request=CreateInboxRequest(username=username, client_id=f"{username}-livekit")
        )
        self.inbox_id = inbox.inbox_id
        self.ws_task: asyncio.Task | None = None

        super().__init__(
            instructions=f"""
            You are a helpful voice assistant with your own email inbox.
            Your address is {inbox.email}. It is your inbox, not the user's.
            Pass "{self.inbox_id}" as the inbox_id parameter of every email tool.
            When you greet the user, tell them your email address.
            """,
            tools=AgentMailToolkit(client=client).get_tools(
                ["list_threads", "get_thread", "get_attachment", "send_message", "reply_to_message"]
            ),
        )

    async def _watch_inbox(self) -> None:
        client = AsyncAgentMail()

        async with client.websockets.connect() as socket:
            await socket.send_subscribe(
                Subscribe(inbox_ids=[self.inbox_id], event_types=["message.received"])
            )

            async for event in socket:
                if isinstance(event, MessageReceivedEvent):
                    self.session.interrupt()
                    await self.session.generate_reply(
                        instructions='Say "I just received an email", then read it to the user.',
                        user_input=event.message.model_dump_json(),
                    )

    async def on_enter(self) -> None:
        self.ws_task = asyncio.create_task(self._watch_inbox())

    async def on_exit(self) -> None:
        if self.ws_task:
            self.ws_task.cancel()
```

In the function that starts the session, replace `Assistant()` with `EmailAssistant()` in the `session.start` call, keeping every other option the file already passes. Then run in the terminal:

```bash
python agent.py console
```

## SDK

Install: `pip install agentmail agentmail-toolkit`.

* Toolkit adapter: `AgentMailToolkit` from `agentmail_toolkit.livekit`. `AgentMailToolkit(client=client).get_tools([...])` returns LiveKit tools by name, and calling it with no argument returns all of them.
* Sync client: `AgentMail()`. Idempotent inbox create: `client.inboxes.create(request=CreateInboxRequest(username=..., client_id=...))`.
* Async events: `AsyncAgentMail()`, then `async with client.websockets.connect() as socket`, `await socket.send_subscribe(Subscribe(inbox_ids=[...], event_types=["message.received"]))`, and iterate the socket for `MessageReceivedEvent`.

Installs and the full SDK surface: `/integrations/sdks-and-cli`.

## Facts

* Requires Python 3.11 or newer and a working agent from LiveKit's Voice AI quickstart.
* Env vars: `AGENTMAIL_API_KEY` and `AGENTMAIL_USERNAME`, in the shell or in `.env.local`.
* `client_id` makes the inbox create idempotent. The first startup creates `<username>@agentmail.to`, and every later startup with the same `client_id` returns that same inbox.
* Toolkit tool names, exactly: `send_message`, `reply_to_message`, `forward_message`, `list_threads`, `get_thread`, `get_attachment`, `update_message`, `list_inboxes`, `get_inbox`, `create_inbox`, `delete_inbox`.
* The create response carries `email`, the address the agent announces, and `inbox_id`, which the model passes to every email tool.
* Toolkit built-ins: while a tool call runs, the agent speaks a short update, and when a call fails, the API's error message goes back to the model as the tool result.
* The WebSocket subscribe frame filters by `inbox_ids` and `event_types`, so the loop wakes only for `message.received`.
* The `message.received` event carries the full message, body included.
* LiveKit calls `on_enter` when the agent enters the session and `on_exit` when it leaves, so the WebSocket lives exactly as long as the conversation.
* WebSocket events are delivered only while the connection is open.
* Usernames are claimed once, for everyone on AgentMail. Username errors carry up to three available variants in `suggestions`.
* The `limit_exceeded` error body includes `name` (`LimitExceededError`), `code`, `message`, `fix`, `resource`, `limit`, `upgrade_url`, and `docs`.

## Not supported

* An inbox cannot mail itself. Send the test email from a different account.
* `get_tools` does not error on a misspelled tool name. Unknown names are skipped, and the agent runs without that tool.
* Creating the inbox without a `client_id` works once, then every restart fails with `already_exists`, because the organization now owns the username.
* This code does not replay events missed while the connection was down. Reconnects and other event types: `/advanced/websockets`.
* Do not trust a spoken recipient address. Have the agent repeat it back before sending, or put the address in the instructions.

## Errors

| Error                                     | HTTP status            | Cause                                                               | Fix                                                                  |
| ----------------------------------------- | ---------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `resource_taken`                          | 403                    | Another organization owns the username                              | Set `AGENTMAIL_USERNAME` to one of the `suggestions`, restart        |
| `already_exists`                          | 403                    | Your own organization created the username outside this `client_id` | Set `AGENTMAIL_USERNAME` to one of the `suggestions`, restart        |
| `limit_exceeded` (`Inbox limit exceeded`) | 403                    | Organization at its plan's inbox limit                              | Follow the error's `fix` field: delete an inbox or use `upgrade_url` |
| `KeyError` on `AGENTMAIL_USERNAME`        | none, at session start | Variable unset where the agent runs                                 | Set it in the shell or `.env.local`                                  |
| 401 on the inbox create                   | 401                    | `AGENTMAIL_API_KEY` missing                                         | Set it in the shell or `.env.local`                                  |

## Verify

Run `python agent.py console`. The agent greets you with its email address, which confirms the inbox create succeeded. Then email that address from another account: within a few seconds the agent interrupts itself, announces the email, and reads it aloud.

## Related

* `/advanced/websockets` to recover events after a dropped connection and subscribe to more than inbound mail
* `/core/conversations` to keep multi-email threads straight across replies and follow-ups
* `/quickstart` for a first AgentMail project and API key
