Deploy
September 20, 2026 · View on GitHub
MailboxBoy is meant to run as a single Compose service on a machine you control. The image contains only the binary. Taxonomy, OAuth, the TypeSafe key, and SQLite stay on the host.
What to copy
Never bake these into the image or commit them:
| Host file | Role |
|---|---|
.env | TYPESAFE_API_KEY=… |
config/credentials.json | Gmail Desktop OAuth client |
config/token.json | User refresh token (mailboxboy auth on a machine with a browser) |
config/taxonomy.yaml | Categories |
data/mailbox.db | Optional; keeps history cursor and review rows |
Authorize on a laptop first, then copy token.json. The daemon cannot complete the localhost OAuth bounce by itself.
Layout on the server
Example used in production: qctech@host:~/App/MailboxBoy.
MailboxBoy/
Dockerfile
docker-compose.yml
.env # gitignored
config/
credentials.json
token.json
taxonomy.yaml
data/
mailbox.db # optional
docker-compose.yml mounts ./config → /config and ./data → /data, and sets:
MAILBOXBOY_LISTEN=:8080MAILBOXBOY_POLL=5sTZ=Asia/Hong_Kongrestart: unless-stopped
The container user is uid 1000. Host files should be owned by that uid (typical qctech / first Linux user). If the bind mount is root-owned, set user: "0:0" in Compose.
Bring-up
On the laptop (after auth + labels init):
mkdir -p config data
cp .env.example .env # then edit
cp ~/.config/mailboxboy/{credentials.json,token.json,taxonomy.yaml} ./config/
cp ~/.local/share/mailboxboy/mailbox.db ./data/ # optional
rsync -az --exclude '.git' --exclude '.env' ./ user@server:~/App/MailboxBoy/
rsync -az .env user@server:~/App/MailboxBoy/.env
rsync -az config/ user@server:~/App/MailboxBoy/config/
rsync -az data/ user@server:~/App/MailboxBoy/data/
On the server:
cd ~/App/MailboxBoy
chmod 700 config data
chmod 600 config/token.json config/credentials.json .env
docker compose up -d --build
docker compose ps
curl -sS http://127.0.0.1:8080/healthz
curl -sS http://127.0.0.1:8080/status
docker compose logs -f
Healthcheck: wget to /healthz every 30s, start period 20s.
Published port: 8080. Restrict it to LAN or localhost if the host is exposed.
Behaviour after start
- Ensures Gmail labels from the taxonomy.
- If SQLite already has
history_id, only newmessageAddedevents are classified. - If the database is empty, the cursor is set to now (no 500-message backfill).
- Each new INBOX message is sent to Jev and labels are applied (
servedefaults to--apply).
Kick without waiting for the poll:
curl -X POST http://127.0.0.1:8080/hooks/sync
With MAILBOXBOY_WEBHOOK_TOKEN:
curl -X POST "http://127.0.0.1:8080/hooks/sync?token=YOUR_TOKEN"
Optional Gmail push
Gmail cannot POST to an arbitrary URL. Instant push requires Google Cloud Pub/Sub:
- Create topic
gmail-mailboxboy. - Grant
gmail-api-push@system.gserviceaccount.compublisher on that topic. - Push-subscribe the topic to
https://your-host/hooks/gmail?token=YOUR_TOKEN(HTTPS required). - Set on the container:
GMAIL_PUBSUB_TOPIC=projects/PROJECT_ID/topics/gmail-mailboxboyMAILBOXBOY_WEBHOOK_TOKEN=YOUR_TOKEN
- Restart. Logs should show
gmail watch registered; expires …(watch is renewed daily; Gmail watches last about seven days).
Polling remains the fallback. A 5s poll is enough for a personal inbox if you skip Pub/Sub.
Update
rsync -az --exclude '.git' --exclude '.env' --exclude 'config/' --exclude 'data/' \
./ user@server:~/App/MailboxBoy/
ssh user@server 'cd ~/App/MailboxBoy && docker compose up -d --build'
Keep config/ and data/ on the server; do not --delete them.
Backup
config/token.json— re-auth if lostdata/mailbox.db— history cursor and review notesconfig/taxonomy.yaml— your category text
Logs and failure
docker compose logs --tail 100
Common cases:
| Symptom | Likely cause |
|---|---|
gmail token missing | config/token.json not mounted or wrong mode |
TYPESAFE_API_KEY is not set | .env missing next to Compose file |
No label or Classification Label updates | Empty decision; current builds skip empty messages.modify |
| Watch errors, poll still works | Pub/Sub topic/IAM; safe to ignore if poll is enough |
Permission denied on token.json | uid 1000 cannot write refreshed tokens |
Local Compose (no remote)
export MAILBOXBOY_CONFIG="$HOME/.config/mailboxboy"
export MAILBOXBOY_DATA="$HOME/.local/share/mailboxboy"
docker compose up -d --build
Requires Docker Engine. The image is mailboxboy:local.