Getting started
Choose one installation method.
Install the app and CLI together with Homebrew:
brew install --cask luxass/tap/imessage-relayOr choose a version from the latest release.
Replace X.Y.Z below with that version, without the leading v:
version="X.Y.Z"curl -fsSLO \ "https://github.com/luxass/imessage-relay/releases/download/v${version}/imessage-relay-${version}-macos-universal.zip"unzip "imessage-relay-${version}-macos-universal.zip"mv "iMessage Relay.app" /Applications/open "/Applications/iMessage Relay.app"The universal app requires macOS 14 or newer. Releases are signed with
Developer ID and notarized by Apple. To start the app at login, enable
Launch at login in Settings after installing it in /Applications.
The cask also installs the imessage-relay command.
Show the version and available options with:
imessage-relay --versionimessage-relay --helpTo use the CLI from the ZIP, run ./imessage-relay from the extracted directory. Keep the .bundle resource
directories beside the executable. The CLI does not require the app to be open.
Building requires Swift 6.3 or newer.
git clone https://github.com/luxass/imessage-relay.gitcd imessage-relayswift buildThe standalone imessage-relay-cli formula is deprecated from November 1, 2026.
To switch from that formula to the combined release, run:
brew uninstall --formula imessage-relay-clibrew install --cask luxass/tap/imessage-relayTry a release candidate
Section titled “Try a release candidate”A release candidate is a preview build for testing before a stable release.
Quit the relay before switching versions. If the stable cask is installed,
uninstall it with brew uninstall --cask luxass/tap/imessage-relay.
If the standalone CLI formula is installed, uninstall it with
brew uninstall --formula luxass/tap/imessage-relay-cli. Then run:
brew install --cask luxass/tap/imessage-relay@rcimessage-relay --versionimessage-relay --helpThe RC cask installs both the app and CLI. It cannot be installed alongside the
stable cask because they install the same app and command. Both use the same configuration.
To return to stable, uninstall imessage-relay@rc and install imessage-relay.
Do not use --zap when switching if you want to keep the app configuration.
Grant access and start the relay
Section titled “Grant access and start the relay”For the app, choose an API token during setup and review the macOS permissions. Grant iMessage Relay Full Disk Access in System Settings > Privacy & Security. If macOS closes the app, reopen it to return to the Permissions step. Finish setup, then open Settings > Status from the menu bar. Under API Token, choose Reveal, then Copy. Export the copied value in the terminal where you run the examples:
export RELAY_TOKEN='paste-the-copied-token-here'If you generate a token, the app uses 256 bits of randomness and stores it in
your login Keychain. It creates its non-secret settings at
~/Library/Application Support/imessage-relay/config.json. Add your local
account ID and send allowlist there, then choose Reload under
Settings > Status > Configuration. An empty allowlist denies every send.
{ "allowRemoteConnections": false, "allowedRecipients": ["person@example.com", "+1 202 555 0123"], "hostname": "127.0.0.1", "maximumMediaBytes": 26214400, "port": 8080, "senderAccountID": "your-local-imessage-account-id"}If you use the CLI, grant Full Disk Access to its terminal or
service and start it with environment variables. For a Homebrew installation,
use imessage-relay instead of ./imessage-relay:
export RELAY_TOKEN='replace-with-a-long-random-token'export RELAY_SENDER_ACCOUNT_ID='your-local-imessage-account-id'export RELAY_ALLOWED_RECIPIENTS='person@example.com,+1 202 555 0123'./imessage-relayThe first send can prompt for Automation > Messages permission. GET /v1/status and GET /v1/sender never trigger this prompt.
Check the database
Section titled “Check the database”From another terminal, check that the relay can read the Messages database:
curl -s localhost:8080/v1/status \ -H "Authorization: Bearer $RELAY_TOKEN" | jqcurl -s 'localhost:8080/v1/conversations?limit=3' \ -H "Authorization: Bearer $RELAY_TOKEN" | jq '.items'Filter by an exact phone number or email address to find its conversations:
curl -sG localhost:8080/v1/conversations \ -H "Authorization: Bearer $RELAY_TOKEN" \ --data-urlencode 'participant=person@example.com' | jq '.items'Use a conversation id to read its messages:
curl -s 'localhost:8080/v1/conversations/CONVERSATION_ID/messages?limit=10' \ -H "Authorization: Bearer $RELAY_TOKEN" | jqSend a message
Section titled “Send a message”Run this request only when you started the relay with an allowlist:
curl -s -X POST localhost:8080/v1/messages \ -H "Authorization: Bearer $RELAY_TOKEN" \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: first-message' \ -d '{"to":"person@example.com","text":"hello"}' | jqThe response contains request_id, correlation_status, messages, media,
and poll_url. Repeat the same request with the same idempotency key to receive
the stored response without another send. Use a new key for a different
message.
The relay waits up to three seconds for the matching Messages rows. Request
poll_url while correlation_status is pending or partial. A send with text
and media can produce separate provider messages. The messages array reports
each message and its current lifecycle status.
You can upload a file for later use:
curl -s -X POST localhost:8080/v1/media \ -H "Authorization: Bearer $RELAY_TOKEN" \ -H 'Content-Type: image/jpeg' \ -H 'X-Filename: photo.jpg' \ --data-binary @photo.jpg | jqTo send the returned media.media_id, use a message request with an existing
conversation_id. Attachment and reply sends require Accessibility permission
for the relay process.
See the API reference for all endpoints and response details.