# Codex Agent Email: Give Codex Its Own Mailbox

Published: September 5, 2026

Provision a real mailbox for an OpenAI Codex agent, pass the API key through .env, send the first email, and read replies over SSE or polling.

Giving a Codex agent email takes three steps: create a mailbox with the Robotomail CLI or API, put the API key in a `.env` file that Codex can read but never sees pasted into the chat, and have the agent send through `POST /v1/mailboxes/{id}/messages`. Replies come back either as a server-sent events stream, a webhook, or a plain poll of the mailbox. This guide walks the whole loop with an OpenAI Codex coding agent, including the sandbox and secret-handling details that trip people up on the first run.

If you have already done this with Claude Code, the shape is identical. See the [Claude Code email setup](/blog/email-claude-code) for that variant.

## Why a Codex agent needs its own address

Codex is good at long-running, file-touching work: refactors, migration branches, dependency bumps, nightly test triage. A lot of that work has an outside-world dependency that arrives by email. A vendor has to confirm an API upgrade. A security team wants a scan result. A human reviewer needs a summary and has to be able to just reply.

If you wire Codex into your own Gmail account with OAuth, every message it sends is from you, every message it reads is yours, and there is no clean audit boundary. A dedicated `codex@` address fixes that. Mail from the agent is identifiable as agent mail, replies land in a mailbox only the agent reads, and revoking access is deleting one mailbox instead of untangling a token.

The general argument is in [why agents need a real inbox](/blog/why-agents-need-real-inbox). The practical version: you want a mailbox you can create and destroy with an API call.

## Step 1: provision the mailbox

CLI is fastest:

```bash
npx @robotomail/cli mailbox create codex-agent
```

Or the API directly:

```bash
curl -X POST https://api.robotomail.com/v1/mailboxes \
  -H "Authorization: Bearer rm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"address": "codex-agent"}'
```

Omit `domainId` and you get an address on the platform domain, which is fine for internal work and testing. If the agent is talking to people outside your company, put it on a domain you control and let recipients see a familiar name. Some teams run a dedicated domain for agent traffic so agent mail never affects reputation on their human domain. Setup is in the [custom domain guide](/docs/guides/custom-domain).

The response includes the mailbox ID and the full address. Save both. The ID goes in the path of every send and read call.

One mailbox per agent role, not per task. `codex-agent` is a good boundary. `codex-refactor-pr-4821` is not, unless you are genuinely spinning up an ephemeral identity per job, which is a legitimate pattern for [multi-agent workflows](/use-cases/multi-agent-workflows) but adds bookkeeping.

## Step 2: hand Codex the key through .env, never the chat

This is the part people get wrong.

Do not paste `rm_...` into a Codex prompt. Chat input becomes part of the transcript, and transcripts get logged, cached, replayed into context on the next turn, and sometimes shipped to a provider for review. A key in the conversation is a key you have to rotate.

Instead, put it in a file the process can read and the agent is told not to print:

```bash
# .env  (add to .gitignore first)
ROBOTOMAIL_API_KEY=rm_your_api_key
ROBOTOMAIL_MAILBOX_ID=mbx_codex
[email protected]
```

```bash
echo ".env" >> .gitignore
```

Then tell Codex about the capability, not the credential. Codex reads `AGENTS.md` in the repo root, so that is the right place:

```markdown
## Email

You have a mailbox at [email protected].

- Credentials live in `.env` as `ROBOTOMAIL_API_KEY`. Read them with your
  language's env loader. Never echo, log, or commit the value.
- Send with `scripts/send_mail.py`. Read replies with `scripts/read_mail.py`.
- Only email addresses listed in `docs/contacts.md`. If a task implies
  emailing anyone else, stop and ask.
- Sign messages as "Codex agent for <team>" and include the task or PR
  reference in the subject.
```

Two things worth noting. First, the allowlist. An agent with unrestricted send is an agent that can be socially engineered into emailing a stranger, and inbound mail is untrusted input by definition. Read [email prompt injection](/blog/email-prompt-injection) before you widen that list.

Second, sandboxing. Codex CLI runs shell commands in a sandbox, and depending on your approval and sandbox mode outbound network access may be blocked. If your first send fails with a connection error rather than an HTTP error, that is almost always the sandbox and not your key. Check your Codex configuration and allow network access for the workspace, or run the send step outside the sandbox.

## Step 3: send the first email

A thin script keeps the key out of the agent's hands and gives you one place to add logging:

```python
# scripts/send_mail.py
import os, sys, json, urllib.request

KEY = os.environ["ROBOTOMAIL_API_KEY"]
MAILBOX = os.environ["ROBOTOMAIL_MAILBOX_ID"]

def send(to, subject, body, in_reply_to=None):
    payload = {"to": [to], "subject": subject, "bodyText": body}
    if in_reply_to:
        payload["inReplyTo"] = in_reply_to
    req = urllib.request.Request(
        f"https://api.robotomail.com/v1/mailboxes/{MAILBOX}/messages",
        data=json.dumps(payload).encode(),
        headers={"Authorization": f"Bearer {KEY}",
                 "Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req) as r:
        return json.load(r)

if __name__ == "__main__":
    to, subject = sys.argv[1], sys.argv[2]
    print(send(to, subject, sys.stdin.read())["id"])
```

The raw call, if you would rather have Codex use curl:

```bash
curl -X POST https://api.robotomail.com/v1/mailboxes/mbx_codex/messages \
  -H "Authorization: Bearer $ROBOTOMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": ["[email protected]"], "subject": "PR #4821 ready for review", "bodyText": "Migration branch is green. Summary in the PR description."}'
```

Ask Codex to send itself a test message first. It proves the key, the sandbox, and the address in one shot. More on the first send in the [send your first email guide](/docs/guides/send-first-email).

## Step 4: read replies over SSE or polling

Codex sessions are interactive and often short. Three options, and the right one depends on how long the agent stays alive.

| Pattern | Good for | Trade-off |
| --- | --- | --- |
| SSE event stream | An agent sitting in a session waiting on a reply | Needs a live connection; reconnect logic on drop |
| Polling | Scripts, CI jobs, cron-driven Codex runs | Latency equals your interval; wasted calls when quiet |
| Webhook | Always-on services that outlive the session | Needs a public HTTPS endpoint |

**SSE.** Robotomail exposes an events stream, so a waiting agent can block on it instead of hammering the API. This is the cleanest fit for "send a question, wait for the human, continue" inside one Codex session. Stream shapes and reconnection semantics are in the [events API reference](/docs/api/events).

**Polling.** Simplest thing that works and the right default for a Codex run kicked off by CI. Do a `GET` against the same messages path you post to, filter for anything newer than your last checkpoint, and back off when idle:

```python
# scripts/read_mail.py  (sketch)
import os, time, json, urllib.request

KEY = os.environ["ROBOTOMAIL_API_KEY"]
MAILBOX = os.environ["ROBOTOMAIL_MAILBOX_ID"]

def fetch():
    req = urllib.request.Request(
        f"https://api.robotomail.com/v1/mailboxes/{MAILBOX}/messages",
        headers={"Authorization": f"Bearer {KEY}"},
    )
    with urllib.request.urlopen(req) as r:
        return json.load(r)

def wait_for_reply(timeout=1800, interval=20):
    deadline = time.time() + timeout
    while time.time() < deadline:
        for m in fetch()["data"]:
            if not m.get("read"):
                return m
        time.sleep(interval)
    return None
```

Field names and pagination parameters are in the [messages API reference](/docs/api/messages) - use those rather than guessing.

**Webhooks.** If the Codex agent is a step inside a longer-lived service, register a webhook and let inbound mail wake it. The payload is a flat JSON envelope:

```json
{
  "event": "message.received",
  "data": {
    "message_id": "...", "mailbox_id": "...",
    "mailbox_address": "[email protected]",
    "from": "[email protected]", "subject": "Re: PR #4821 ready for review",
    "body_text": "Looks good, ship it.", "thread_id": "...",
    "received_at": "2026-04-17T10:00:00.000Z"
  }
}
```

Note the case change: sends take camelCase (`bodyText`), webhook payloads arrive snake_case (`body_text`). Write your parser accordingly.

## Keep replies in the thread

When Codex answers, pass the inbound `message_id` as `inReplyTo` on the send. That sets the correct `In-Reply-To` and `References` headers, so the reply threads properly in the recipient's client instead of showing up as a fresh message with a `Re:` subject. Recipients notice the difference immediately, and so do spam filters. Details in [threading](/docs/concepts/threading) and the [receive and reply guide](/docs/guides/receive-and-reply).

## FAQ

### Can I just use my Gmail account with the Codex agent?

You can, and it works until it does not. OAuth scopes are broad, the agent's mail is indistinguishable from yours, and Google's quotas were designed for a human clicking send. A separate mailbox per agent is cleaner to audit and cheaper to revoke. Comparison in [Gmail API vs Robotomail](/compare/gmail-api).

### What stops the agent from emailing the wrong person?

An allowlist you enforce in your send wrapper, not in the prompt. Check the recipient against a static list in `scripts/send_mail.py` and raise on anything unexpected. Instructions in `AGENTS.md` guide behavior; code enforces it.

### Should each Codex task get its own mailbox?

Usually no. One mailbox per agent role, and use `thread_id` to keep separate conversations distinct. Per-task mailboxes make sense only when the task itself is a distinct identity, like a per-customer intake agent.

### Does this work with other coding agents?

Yes, nothing here is Codex specific beyond `AGENTS.md` and the sandbox note. The same mailbox, key handling, and read patterns apply to any agent. See the general [agent email address setup](/how-to-give-your-ai-agent-email-address).

Ready to wire it up? Create a mailbox and send the first message in a few minutes with the [quickstart](/docs/quickstart), or start at [robotomail.com](https://robotomail.com).
