MSGET gives OpenClaw-compatible apps, MCP tools, and Hermes Agent access to Apple messaging through a service you host. Use the browser dashboard for account setup, Apple activation, and interface settings.
Messages pass between your deployment and Apple, not through MSGET's hosted services. Those services handle account access and activation.
Website · Image tags · Broker image · Dashboard image
MSGET is available only as these container images.
Using an agent to set this up? See Setup guide for agents.
You need:
podman compose (see the Podman note below). The core mounts the dashboard image, which needs image mounts.This example runs on your computer. On a server, use the same file and see Remote access.
MSGET runs two containers. The core, msget/msget-app, stores your data and talks to Apple. The broker, msget/msget-broker, stores nothing. It makes a fixed set of calls to MSGET's account and relay services for the core over the Unix socket /run/msget-ipc/broker.sock, on the shared msget-ipc volume. It cannot read /state, listens on no network port, and accepts only the core's user (UID 65532). It takes no arguments. Keep both images on the same version.
The browser dashboard is a third image, msget/msget-dashboard. It contains only the dashboard files and serves linux/amd64 and linux/arm64 hosts alike. Do not run it as a container or add it as a third service. The core mounts it read-only at /usr/share/msget/dashboard and checks every file at startup. If the dashboard does not support the core's version, the core stops with a dashboard packaging error. The latest tags of the three images work together.
Save the following as compose.yaml:
services:
msget:
image: msget/msget-app:latest
restart: unless-stopped
user: "65532:65532" # Keep as written: required for the broker connection and your saved data.
group_add:
- "65534" # Keep as written: required for sign-in and activation through the broker.
ports:
- "127.0.0.1:8739:8739"
volumes:
- msget-state:/state
- msget-ipc:/run/msget-ipc
- type: image # Keep as written: the browser dashboard, read-only.
source: msget/msget-dashboard:latest
target: /usr/share/msget/dashboard
networks:
- msget-core
depends_on:
- msget-broker
msget-broker:
image: msget/msget-broker:latest
restart: unless-stopped
user: "65533:65534" # Keep as written: the broker's own user; it cannot read your saved data.
read_only: true # Keep as written: the broker saves nothing and needs no writable files.
cap_drop:
- ALL # Keep as written: the broker needs no extra system permissions.
volumes:
- msget-ipc:/run/msget-ipc
networks:
- msget-broker
volumes:
msget-state:
msget-ipc:
networks:
msget-core:
msget-broker:
depends_on only sets start order. If the broker is not ready, sign-in or activation may report unavailable. Retry the step.
The host needs normal outbound internet access and system DNS that returns real answers:
init-p01st.push.apple.com, TCP 5223 to *.push.apple.com, and TCP 443 to Apple services.auth.msget.app and relay.msget.app.MSGET has no proxy settings. It does not support hosts that force traffic through an intercepting proxy (including some managed bot VMs), fake-IP DNS, or 443-only egress.
This is the Standard setup. For the Docker-only Strong setup, which also blocks direct core access to MSGET's services at the host firewall, see apps/deployment/docs/network-isolation.md.
For rootless Podman, separate network names may not isolate the networks. Add this as compose.podman.yaml:
networks:
msget-core:
driver_opts:
isolate: "strict"
msget-broker:
driver_opts:
isolate: "strict"
Start with podman compose -f compose.yaml -f compose.podman.yaml up -d.
isolate=strict (checked with Podman 5.8.7 and Netavark 1.17.2). Some older releases accept it without enforcing it. Run the broker-namespace probes in apps/deployment/docs/network-isolation.md; if a probe reaches the dashboard, the setup is not isolated.user: and group_add: as written. Rootless Podman maps them into your user namespace. Your account needs subuid and subgid ranges of at least 65536 each, the usual default.:U or a compatible keep-id mapping so 65532:65532 can write /state and 65533:65534 can write /run/msget-ipc.loginctl enable-linger "$USER" with a user systemd service, convert both services to Quadlet units, or run rootful Podman under systemd.type: image volume to Podman (checked with Podman 5.8.9 and podman-compose 1.6.0). Without the mount, the core stops with a dashboard packaging error.Keep 127.0.0.1 in host port mappings for local use. A mapping without it, such as 8739:8739, or one using 0.0.0.0, is reachable from other machines. Use that only for remote access, with firewall and access controls. MSGET listens on 0.0.0.0:8739 inside the container so Docker can forward it; the host mapping sets who can reach it.
No environment variables are needed. Settings, including log level (off, error, warn, info, debug, or trace; default info), are in the dashboard. Only the owner can change the log level. The broker's optional MSGET_BROKER_EXTRA_ROOT_CA_FILE is only for traffic capture; see apps/deployment/docs/traffic-capture.md.
From the folder with compose.yaml, run:
docker compose up -d
docker compose ps
When both are running, open http://localhost:8739. If setup does not open, see Troubleshooting.
In the browser:
docker compose logs msget, then enter the setup code in the dashboard. Keep it secret: that browser can claim the deployment for 30 minutes. Restarting msget replaces the code and cancels unused grants.Setup is done when the client connects. Port mappings make an interface reachable; the dashboard turns it on or off.
For terminal setup, the image includes msgetctl. It connects to the running service through a local socket in the container, with no setup code, separate install, or extra port.
# Prompts in your terminal. Passwords are hidden.
docker compose exec msget /usr/local/bin/msgetctl onboard --interactive
# Current stage and the next permitted commands, as JSON.
docker compose exec -T msget /usr/local/bin/msgetctl status --json
Telegram sign-in and account sign-in changes still need the dashboard. For agent setup, install the msget Agent Skill from https://github.com/msget-app/msget and see "Setup guide for agents" below. A read-only root filesystem also needs a writable /tmp, such as tmpfs: [/tmp].
| Interface | Container port | Purpose |
|---|---|---|
| Web dashboard | 8739 | Account access, guided setup, configuration, and status |
| JSON-RPC over TCP | 8741 | OpenClaw-compatible messaging interface |
| MCP over Streamable HTTP | 8742 | MCP endpoint at /mcp |
| BlueBubbles-compatible gateway | 1234 | Hermes Agent integration |
Publish only the interfaces you enable, and keep the dashboard mapping:
- "127.0.0.1:8741:8741" # JSON-RPC
- "127.0.0.1:8742:8742" # MCP
- "127.0.0.1:1234:1234" # BlueBubbles
MCP attachments, including symlink targets, must be inside /state/attachments. Grant access through dashboard permissions.
All interfaces share one MSGET service. Its BlueBubbles support is aimed at Hermes Agent and does not cover the full BlueBubbles Server.
MSGET runs as a persistent service. Clients do not own the service or its Apple connection. Use JSON-RPC over TCP or MCP over Streamable HTTP. Their stdio forms are retired; do not pass --mcp or --mcp=stdio.
With the loopback mapping above, the MCP endpoint is http://localhost:8742/mcp. A stdio-only client needs an external stdio-to-HTTP bridge connected to the running service. Do not start another MSGET for it.
The example keeps host ports on this computer. For tools on another machine, route the ports they need through a private network or proxy.
To reach the dashboard over SSH, run this on your computer and keep the session open:
ssh -L 8739:127.0.0.1:8739 user@your-server
Then open http://localhost:8739 locally. This forwards only the dashboard.
For LAN access, publish 8739 on the host's LAN address and open that address. For an HTTPS reverse proxy, send traffic to 8739, preserve the browser-facing Host header (including a non-default port), and do not trust forwarded headers. No public URL setting is needed.
Sessions are tied to the origin used at sign-in, so switching between localhost, LAN, and proxy addresses means signing in again. HTTPS origins get Secure cookies. The dashboard requires sign-in, same-origin browser changes, and CSRF protection. Because it validates the request Host instead of a fixed allowlist, DNS-rebinding protection is weaker. Limit network exposure.
/state holds deployment ownership, settings, and saved Apple connection state. MSGET manages its file paths and hosted account and relay endpoints. Treat it as sensitive./run/msget-ipc holds only the broker's live socket. It has no durable data; the broker recreates the socket on start.The core runs as 65532:65532 plus group 65534 for the socket directory. The broker runs as 65533:65534 and cannot read /state. Named volumes get these owners from the images. With host bind mounts, give those users write access; broker user 65533 must own /run/msget-ipc, with no access for other users (chmod 0750).
Upgrades preserve default filenames and existing /state volumes. Move files from custom paths in older setups into the standard layout before upgrading.
Recreating containers keeps named volumes. docker compose down removes containers but keeps volumes; adding -v deletes them and the saved data. For backup, restore, and deletion, see apps/deployment/docs/backup-and-deletion.md.
Message contents pass between the core and Apple, never through MSGET's hosted services. The broker performs only a fixed set of 29 operations there: account sign-in, session checks, activation, Telegram linking, and relay setup.
The dashboard owner view shows the broker's contact count for each operation. Run docker compose exec msget /usr/local/bin/msgetctl privacy check to test from the core's network whether MSGET's services are directly reachable.
apps/deployment/docs/broker-data.md: each operation's destination and the data sent, returned, and stored.apps/deployment/docs/network-isolation.md: Standard and Docker Strong setups.apps/deployment/docs/traffic-capture.md: inspect broker TLS traffic with your own CA and proxy.apps/deployment/docs/backup-and-deletion.md: back up, restore, or wipe local data.latest follows published releases of msget/msget-app, msget/msget-broker and msget/msget-dashboard. To pin a version, set the same tag or digest for the core and the broker in compose.yaml, using the Image tags page. The dashboard has its own versions on its tags page; pin the tag or digest of a dashboard version that supports the core's version in the image volume's source:.
Release indexes are signed, and each platform image has a signed SPDX JSON SBOM attestation. Pin updates by digest. Before first use of a new digest, get the public key at apps/deployment/docs/msget-image.pub in the release source. Verify the index, list each platform digest, then verify the SBOM on your platform digest:
cosign verify --insecure-ignore-tlog=true \
--key msget-image.pub \
docker.io/msget/msget-app@sha256:<index-digest>
docker buildx imagetools inspect \
docker.io/msget/msget-app@sha256:<index-digest>
cosign verify-attestation --insecure-ignore-tlog=true \
--key msget-image.pub \
--type spdxjson \
docker.io/msget/msget-app@sha256:<platform-digest>
Repeat for the broker and the dashboard, replacing docker.io/msget/msget-app@sha256:<digest> with docker.io/msget/msget-broker@sha256:<digest> or docker.io/msget/msget-dashboard@sha256:<digest> in each command.
Back up the state volume, then pull and restart:
docker compose pull
docker pull msget/msget-dashboard:latest
docker compose up -d --force-recreate msget
docker compose pull does not pull the dashboard image, so pull it by name. docker compose up pulls it only when it is missing. Recreate the core after a dashboard update so that it mounts the new dashboard image.
Then check that the dashboard opens, your setup is intact, and your interfaces are ready.
To read the logs, run:
docker compose logs --tail=100 -f msget
| Symptom | Check |
|---|---|
| Container exits or restarts | Read the startup error in the logs. With bind mounts, check that 65532:65532 can write /state and 65533:65534 can write /run/msget-ipc. Broker user 65533 must own /run/msget-ipc, with no access for others (chmod 0750). |
| Core stops with a dashboard packaging error | Check that the type: image volume is present as written and that your Docker or Podman version supports image mounts. Pull msget/msget-dashboard again, and use a dashboard version that supports the core's version (the latest tags work together). |
| Sign-in or activation unavailable | Check msget-broker with docker compose ps and docker compose logs msget-broker. The core reaches MSGET's hosted services only through the broker. |
| Dashboard does not open | Check the port mapping or SSH/proxy route. A reverse proxy must preserve the browser's Host header. |
| Interface is unavailable | Check Apple activation and whether the interface is enabled and ready in the dashboard. Then check the client endpoint and route. |
| Client expects a stdio command | Use JSON-RPC over TCP or MCP over Streamable HTTP. For a stdio-only MCP client, use a stdio-to-HTTP bridge. |
Install the msget Agent Skill and follow it:
npx skills add msget-app/msget # or any Agent Skills installer
docker compose exec -T msget /usr/local/bin/msgetctl --help
msgetctl status --json. Follow awaiting and nextActions: do agent steps yourself and stop for the user at human steps. Keep the user involved, especially for Apple verification.0600 file with --env-file PATH|-, or let the user run onboard --interactive. For Apple activation, ask for the Apple ID in plaintext, place the password file at a private path inside the container, and use --apple-id EMAIL with --apple-password-file PATH. The msget skill covers the file format, upload route, and a dangerous plaintext-password argument. Use that argument only with the user's explicit consent.--code-stdin; submit MSGET email codes as MSGET_EMAIL_CODE in the same --env-file - input as the account email. Never repeat codes in reports.msgetctl wait for background steps. Do not write sleep loops.docker exec.status, then test with a harmless status or capability request. Do not send a live message.Content type
Image
Digest
sha256:34dcd7df9…
Size
37.1 MB
Last updated
12 days ago
docker pull msget/msget-app