Copy/paste this prompt into Codex on a Mac that can reach a BlueBubbles server. It describes the setup I want Codex to build and verify. Replace placeholders before running it.
Set up a private "iMessage Codex" bridge on this Mac.
Goal
- I want to text Codex over iMessage through BlueBubbles.
- Allowed inbound iMessages should be forwarded to Codex with no command prefix.
- Codex should reply back into the same iMessage chat.
- Images I send in iMessage should be downloaded from BlueBubbles and passed to Codex as local image inputs.
- If a text message includes image attachments, include the downloaded local file paths in the Codex prompt so Codex can copy/edit the exact files.
- The bridge should send read receipts and typing indicators through BlueBubbles while Codex is working.
- The bridge should recover from stuck Codex turns: timeout, send a short iMessage notice, restart the Codex worker, and rotate away from the stuck Codex thread.
- Keep secrets out of logs and docs.
Architecture
- BlueBubbles server runs on the Mac or a reachable macOS VM that is signed into iMessage.
- A local Python HTTP relay listens on a host/port such as 0.0.0.0:8646.
- BlueBubbles sends incoming-message webhooks to:
http://<relay-host>:8646/bluebubbles-webhook?password=<relay-webhook-secret>
- The relay starts `codex app-server --listen stdio://` and talks to it over JSON-RPC stdio.
- The relay sends replies back through BlueBubbles' text message API.
- The relay also polls recent BlueBubbles messages as a fallback, because webhooks can occasionally miss events.
Create these files
- `~/.imessage-codex/relay.py`
- `~/.imessage-codex/.env`
- `~/Library/LaunchAgents/com.<owner>.imessage-codex-relay.plist`
- `~/.imessage-codex/README.md`
Environment template
Create `~/.imessage-codex/.env` with values like this:
BLUEBUBBLES_URL=http://<bluebubbles-host>:1234
BLUEBUBBLES_PASSWORD=<bluebubbles-api-password>
IMESSAGE_WEBHOOK_SECRET=<random-shared-secret-for-webhook-url>
IMESSAGE_ALLOW_FROM=<comma-separated-phone-numbers-or-emails-allowed-to-use-the-bot>
IMESSAGE_RELAY_HOST=0.0.0.0
IMESSAGE_RELAY_PORT=8646
IMESSAGE_ENABLE_POLLING=true
IMESSAGE_POLL_INTERVAL=5
IMESSAGE_SEND_READ_RECEIPTS=true
IMESSAGE_SEND_TYPING=true
IMESSAGE_TYPING_INTERVAL=15
IMESSAGE_DEBOUNCE_SECONDS=4
IMESSAGE_SEND_ERRORS=false
IMESSAGE_MAX_ATTACHMENT_BYTES=26214400
CODEX_BIN=/opt/homebrew/bin/codex
CODEX_BACKEND=app-server
CODEX_CWD=<default-working-directory-for-codex>
CODEX_APPROVAL_POLICY=never
CODEX_SANDBOX=danger-full-access
CODEX_TURN_TIMEOUT=120
CODEX_APP_SERVER_INIT_TIMEOUT=90
Relay requirements
Implement the relay in Python 3 using only the standard library unless there is already a local preference for another stack.
The relay should:
- Load only `BLUEBUBBLES_*`, `IMESSAGE_*`, and `CODEX_*` values from `.env` plus process env.
- Expose `GET /health`, returning JSON with `ok` and the current Codex `thread_id`.
- Expose `POST /bluebubbles-webhook?password=...`.
- Reject webhook requests with the wrong `IMESSAGE_WEBHOOK_SECRET`.
- Ignore messages from myself.
- Ignore senders not listed in `IMESSAGE_ALLOW_FROM`, unless that variable is blank.
- Accept every inbound message from an allowed sender; do not require a command prefix.
- Debounce rapid webhook events per chat for `IMESSAGE_DEBOUNCE_SECONDS`, then batch the collected text/images into one Codex turn. This prevents rich link previews or quick follow-up texts from spawning duplicate Codex turns.
- Deduplicate webhook and poller events by message guid.
- Mark accepted BlueBubbles chats read when `IMESSAGE_SEND_READ_RECEIPTS=true`.
- Send BlueBubbles typing actions every `IMESSAGE_TYPING_INTERVAL` seconds while Codex is running.
- Query BlueBubbles recent messages every `IMESSAGE_POLL_INTERVAL` seconds when polling is enabled.
- Download image attachments from BlueBubbles:
`/api/v1/attachment/{attachmentGuid}/download?password=<BLUEBUBBLES_PASSWORD>`
- Store downloaded images under:
`~/.imessage-codex/attachments/<message-guid>/<safe-filename>`
- Pass downloaded images to Codex app-server as native local image inputs.
- For image-only messages, send only the local image inputs to Codex.
- For text+image messages, send the text plus a short list of local file paths.
- Send Codex's final text reply to BlueBubbles through:
`POST /api/v1/message/text?password=<BLUEBUBBLES_PASSWORD>`
Codex app-server behavior
- Start Codex with:
`codex app-server --listen stdio://`
- Initialize the app-server client with a title like "iMessage Codex".
- Use a persistent thread id stored in `~/.imessage-codex/state.json`.
- Start a new thread if there is no thread id or resume fails.
- Use developer instructions like:
"You are iMessage Codex, reached through a private iMessage bridge. Reply in plain text for iMessage: brief, direct, no Markdown tables. Keep work tightly scoped for phone requests. Avoid long-running browser or screenshot verification unless essential. Do not claim to have sent files or performed side effects unless you actually did."
- Run turns with:
- `cwd` from `CODEX_CWD`
- approval policy from `CODEX_APPROVAL_POLICY`
- sandbox policy from `CODEX_SANDBOX`
- timeout from `CODEX_TURN_TIMEOUT`
- On timeout:
- stop typing
- archive the current thread id in `state.json` under `archived_thread_ids`
- remove the active `thread_id`
- terminate the Codex app-server process
- send this iMessage, or similar:
"I got stuck and restarted the iMessage Codex worker. The last task may be incomplete; send a short follow-up, or use the Codex app for longer repo/browser work."
BlueBubbles setup
- In BlueBubbles, create a webhook pointing to:
`http://<relay-host>:8646/bluebubbles-webhook?password=<IMESSAGE_WEBHOOK_SECRET>`
- Include message payloads with chats, handles, data text, and attachments if BlueBubbles has options for payload fields.
- Verify that the BlueBubbles API password in `.env` can query messages, send text messages, send typing actions, mark chats read, and download attachments.
LaunchAgent
- Install a user LaunchAgent that runs:
`/usr/bin/python3 ~/.imessage-codex/relay.py`
- Use `RunAtLoad=true` and `KeepAlive=true`.
- Send stdout/stderr to:
`~/.imessage-codex/launchd.out.log`
`~/.imessage-codex/launchd.err.log`
- Load with:
`launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.<owner>.imessage-codex-relay.plist`
Verification
- Run `python3 -m py_compile ~/.imessage-codex/relay.py`.
- Start the LaunchAgent.
- Check:
`curl -s http://127.0.0.1:8646/health`
- Send a simple iMessage from an allowed sender, for example:
`ping`
- Confirm:
- the chat is marked read
- typing indicator appears while Codex runs
- Codex replies in the same iMessage chat
- `~/.imessage-codex/state.json` stores a thread id
- Send an image-only iMessage.
- Confirm:
- the image downloads under `~/.imessage-codex/attachments`
- Codex can describe or use the image
- Send a text+image iMessage asking Codex to use the exact file.
- Confirm:
- Codex receives native image input
- the prompt also includes the local image path
Operational notes
- This is a private bridge. Keep `IMESSAGE_ALLOW_FROM` narrow.
- Do not print BlueBubbles passwords or webhook secrets in logs.
- iMessage delivery depends on the BlueBubbles host remaining signed into iMessage.
- Replies are text-only unless you intentionally add BlueBubbles attachment-send support.
- Incoming iMessage image attachments are supported.
- Long repo/browser tasks are better in the normal Codex app; the phone bridge should timeout and recover rather than stay stuck.