Provider-neutral terminal access to iMessage infrastructure through
imessage-sdk.
imessage-cli is designed for both local users and AI agents. It bundles every official provider,
keeps one provider connection per command, accepts flags or validated JSON input, and emits stable
machine-readable results.
Install globally:
npm install --global imessage-cliOr run it without a global installation:
npx imessage-cli --helpThe CLI requires Node.js ^20.19.0, ^22.13.0, or >=24.
The repository includes an opt-in end-to-end suite that invokes the built CLI against real provider accounts. It sends messages and performs supported provider mutations, but does not start a webhook server or create a tunnel.
Create an ignored repository-root .env.cli-test file with the credentials and dedicated test
recipient/assets for all four providers. It must explicitly opt in:
IMESSAGE_CLI_RUN_LIVE=1
BLOOIO_API_KEY=
BLOOIO_FROM_NUMBER=
COMMS_API_KEY=
IMESSAGE_CLI_TEST_RECIPIENT=
IMESSAGE_CLI_TEST_IMAGE_URL=
IMESSAGE_CLI_TEST_VIDEO_URL=
IMESSAGE_CLI_TEST_FILE_URL=
PHOTON_PROJECT_ID=
PHOTON_PROJECT_SECRET=
PHOTON_PHONE_NUMBER=
SENDBLUE_API_KEY=
SENDBLUE_API_SECRET=
SENDBLUE_FROM_NUMBER=Then run:
pnpm --filter imessage-cli test:integrationTo verify default saved connections instead of environment credentials, keep the fixture variables
in .env.cli-test and run:
IMESSAGE_CLI_USE_SAVED_CONNECTIONS=1 \
pnpm --filter imessage-cli test:integrationIn this mode, the spawned CLI processes receive none of the provider credential environment
variables, so each --provider selection must resolve its saved default connection.
The test file is ignored by Git. The runner loads it directly, redacts known secrets from its own failure messages, and uses only per-command environment credentials—no persistent CLI connection or keychain record is created.
Every provider published from packages/providers/* is bundled with the CLI:
blooio
comms
photon
sendblue
Inspect the installed providers and their normalized capabilities:
imessage-cli provider list
imessage-cli provider show blooio
imessage-cli provider show photon --jsonUse provider environment variables or a saved connection and send directly:
imessage-cli send \
--provider blooio \
--to +15551234567 \
--text 'Hello'Addresses may be written as a phone number, email address, or an explicit address:
+15551234567
hello@example.com
phone:+15551234567
email:hello@example.com
Send through an existing provider-native conversation:
imessage-cli send \
--provider photon \
--conversation 'provider-conversation-id' \
--text 'Hello again'Send a reply:
imessage-cli send \
--provider blooio \
--conversation 'provider-conversation-id' \
--text 'Replying in this thread' \
--reply-to 'provider-message-id' \
--reply-part 0Attachment flags are repeatable and accept a public HTTP(S) URL or local path:
imessage-cli send \
--provider photon \
--to +15551234567 \
--text 'See these files' \
--image ./screenshot.png \
--video https://cdn.example.com/video.mp4 \
--file ./report.pdfCurrent provider restrictions still apply:
- Blooio requires public attachment URLs.
- Comms currently supports plain-text messages only.
- Photon accepts URLs and local files.
- Sendblue accepts one URL or local file per message.
- Group messaging remains disabled for the current official provider configurations.
For predictable memory use, local attachments are read sequentially and limited to 100 MiB in total per command. Provider limits may be lower.
The CLI never uploads a local Blooio attachment to third-party storage implicitly.
Use --input - to read a complete validated input document from stdin. --json controls the
output format; it does not change how stdin is parsed.
imessage-cli send \
--provider photon \
--input - \
--json <<'JSON'
{
"to": [
{
"kind": "phone",
"value": "+15551234567"
}
],
"text": "Generated by an agent",
"attachments": [
{
"kind": "image",
"source": {
"type": "path",
"path": "./screenshot.png"
}
}
],
"idempotencyKey": "agent-run-018fd6"
}
JSONAn input file works as well:
imessage-cli send --provider photon --input ./send.json --jsonInput flags and --input are mutually exclusive. Discover the current agent-facing schema with:
imessage-cli schema
imessage-cli schema send --jsonValidate a send without contacting the provider:
imessage-cli send \
--provider blooio \
--to +15551234567 \
--text 'Hello' \
--dry-run \
--jsonThere are three credential modes.
Create a durable named connection:
imessage-cli connection add support --provider blooioInteractive input is masked where appropriate. Credentials and selected sending identities are stored in the operating system credential store:
- macOS Keychain
- Windows Credential Manager
- Linux Secret Service when available
On headless Linux, the native keyring dependency may fall back to the kernel keyring, whose entries can be session-scoped. Use a Secret Service daemon for durable saved connections, or use environment variables when no durable operating-system credential service is available.
The JSON configuration contains only connection metadata and non-sensitive behavioral settings. It is written with user-only permissions. The CLI never falls back to a plaintext credential file.
The first saved connection for a provider becomes that provider's default. It can then be selected with either form:
imessage-cli send --provider blooio --to +15551234567 --text 'Hello'
imessage-cli send --connection support --to +15551234567 --text 'Hello'Manage saved connections:
imessage-cli connection list
imessage-cli connection show support
imessage-cli connection doctor support
imessage-cli connection doctor support --offline
imessage-cli connection remove supportshow reports credential presence and masks identities. It never prints secret values. doctor
performs only non-mutating checks and never sends a message. remove deletes local configuration
and keychain entries; it does not change the remote provider account or revoke an API key.
Set or replace one credential safely from a masked prompt:
imessage-cli connection credential set support webhookSecretFor non-interactive setup, read it from stdin:
imessage-cli connection credential set support webhookSecret --input - < secret-value-fileEnvironment variables provide an ephemeral automation and CI interface:
| Provider | Variables |
|---|---|
| Blooio | BLOOIO_API_KEY, BLOOIO_FROM_NUMBER, BLOOIO_WEBHOOK_SECRET |
| Photon | PHOTON_PROJECT_ID, PHOTON_PROJECT_SECRET, PHOTON_PHONE_NUMBER, PHOTON_WEBHOOK_SECRET |
| Sendblue | SENDBLUE_API_KEY, SENDBLUE_API_SECRET, SENDBLUE_FROM_NUMBER, SENDBLUE_WEBHOOK_SECRET |
Environment values override a saved connection for the current process and are never persisted.
Provider credential options can be supplied for one invocation:
imessage-cli send \
--provider blooio \
--api-key "$BLOOIO_API_KEY" \
--from-number +15551234567 \
--to +15557654321 \
--text 'Hello'One-time options are never saved or logged by the CLI. Supplying a literal secret as a command-line argument is nevertheless discouraged because operating-system process inspection and shell history may expose it. Prefer a saved connection, a masked prompt, or environment injection.
Resolution order is:
command option
-> environment variable
-> selected/default saved connection
-> provider default
Machine-readable and non-interactive commands never prompt. A missing required value becomes a structured error.
The CLI webhook server is experimental in the initial release. You must explicitly acknowledge
that status with --experimental; otherwise the command exits with a usage error before loading
provider credentials or binding a port. The server application is implemented with Hono and runs
through Hono's official Node.js adapter.
Start a signed-webhook receiver on the loopback interface:
imessage-cli webhook serve \
--experimental \
--provider blooio \
--host 127.0.0.1 \
--port 8787 \
--path /webhooks \
--jsonThe server:
- passes the untouched body and headers to the provider verifier;
- emits normalized events as JSON Lines on stdout;
- writes startup information and diagnostics to stderr;
- returns
204for an accepted webhook, including an irrelevant but valid event; - returns
401for an invalid signature; - limits request body size;
- exposes
GET /healthzby default; - closes the selected SDK client on
SIGINTorSIGTERM.
It binds 127.0.0.1 by default and does not install or supervise a tunnel.
Run the webhook server in one terminal and ngrok in another:
ngrok http 8787If ngrok assigns https://example.ngrok.app, register this full callback URL in the provider
dashboard:
https://example.ngrok.app/webhooks
For private message content, consider disabling ngrok request inspection:
ngrok http 8787 --url https://example.ngrok.app --inspect=falsecloudflared tunnel --url http://127.0.0.1:8787Append /webhooks to the generated HTTPS origin. Quick Tunnel hostnames are temporary and may need
to be updated in the provider dashboard after a restart.
The webhook command is intended for local development and agent experiments. It does not provide a durable queue, high availability, delivery persistence, or replay ledger. Production webhook consumers should deploy the SDK handler behind a stable HTTPS endpoint.
Normalized webhook events can be piped into any program:
imessage-cli webhook serve --experimental --provider photon --json | node ./my-agent.mjsThat program can invoke its preferred AI model and reply with a separate imessage-cli send
command. The CLI does not bundle a model SDK or tunnel provider.
message get
conversation open
conversation get
conversation mark-read
attachment download
reaction add
reaction remove
typing start
typing stop
Provider-specific extensions remain namespaced:
provider blooio numbers list
provider blooio message status
provider photon line show
provider sendblue message status
provider sendblue tapback add
Unsupported normalized operations return UnsupportedCapabilityError; the CLI does not silently
approximate provider behavior.
--json writes one success object to stdout:
{
"schemaVersion": 1,
"ok": true,
"command": "send",
"context": {
"provider": "blooio",
"connectionId": "support"
},
"data": {
"id": "provider-message-id"
}
}Failures are written to stderr:
{
"schemaVersion": 1,
"ok": false,
"command": "send",
"error": {
"type": "AmbiguousDeliveryError",
"code": "ambiguous_delivery",
"message": "The provider may have accepted the message.",
"retryable": true,
"deliveryAmbiguous": true,
"safeToRetry": false
}
}Provider raw payloads, credentials, and authorization headers are omitted. The CLI never retries
an ambiguous send automatically.
Exit codes:
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Unexpected internal failure |
2 |
Invalid command input, configuration, or local credential store |
3 |
Authentication failure |
4 |
Unsupported or permanent provider operation |
5 |
Rate limit or provider availability failure |
6 |
Ambiguous message delivery; do not retry blindly |
130 |
Interrupted with SIGINT |
143 |
Terminated with SIGTERM |
imessage-cli completion bash
imessage-cli completion zsh
imessage-cli completion fish
imessage-cli completion powershellFollow the installation convention for the selected shell and source the generated script.