# AGENTS.md - Mattermost Push Proxy Router ## Project Summary Stateless Go HTTP router that sits in front of Mattermost push notification backends. It implements the `/api/v1/send_push` and `/api/v1/ack` protocol used by Mattermost servers, routing traffic by mobile client platform prefix. ## Problem It Solves Mattermost exposes a single server-wide `PushNotificationServer` URL. On mixed deployments (official mobile app + custom client like Bubbles), one proxy cannot serve both: official apps need HPNS keys, custom apps need their own APNs key/topic. This router forwards: - Custom prefixes (e.g. `apple_bubbles`) → your stock `mattermost-push-proxy` - Official prefixes (e.g. `apple_rn`, `android_rn`) → HPNS relay ## Tech Stack - **Backend**: Go 1.26+ (`github.com/BurntSushi/toml` for config) - **Protocol**: Mattermost push proxy API (`POST /api/v1/send_push`, `POST /api/v1/ack`) - **Deployment**: Single binary, Docker, GoReleaser ## Project Structure ``` main.go — CLI entry point server.go — HTTP server and route handlers router.go — Platform extraction from push payloads proxy.go — Upstream forwarding config.go — Environment-based configuration Makefile — Build/test/lint targets ``` ## Build & Run ```bash make build CONFIG_FILE=config/router.toml ./mattermost-push-proxy-router make test ``` Default port: **8066** (from config `listen`, overridable via `PORT`). ## Routing Logic Device IDs are registered as `platform:token` (e.g. `apple_bubbles:`). The Mattermost server splits this into JSON fields before POSTing to the push service: - `platform` → prefix used for routing - `device_id` → bare APNs/FCM token The router reads `platform` (with `-vN` version suffix stripped) and matches it against configured routes. ## Configuration Routes are defined in a TOML file referenced by `CONFIG_FILE`: ```toml listen = ":8066" [[routes]] prefixes = ["apple_bubbles"] url = "http://bubbles-proxy:8067" [[routes]] prefixes = ["apple_rn", "android_rn"] url = "https://push.mattermost.com" ``` | Variable | Description | |---|---| | `CONFIG_FILE` | Path to router TOML config (required) | | `PORT` | Override listen port | | `REQUEST_TIMEOUT_SEC` | Upstream HTTP timeout | ## Architecture ``` Mattermost server └─ PushNotificationServer └─ [this router :8066] ├─ apple_bubbles:* → custom mattermost-push-proxy (:8067) └─ apple_rn:* / android_rn:* → HPNS (push.mattermost.com) ``` ## Key Conventions - Config via TOML file (`CONFIG_FILE`); HTTP/API handling uses stdlib only - Responses follow push-proxy shape: `{"status":"OK"}`, `{"status":"FAIL","error":"..."}` - Unknown platforms return `FAIL` (same as stock proxy dropping unmatched types) ## Metrics Prometheus metrics are exposed at `GET /metrics`: | Metric | Labels | Description | | --- | --- | --- | | `router_requests_total` | `path`, `platform` | Requests routed to a backend | | `router_routing_errors_total` | `reason` | Requests rejected before forwarding (`read_body`, `invalid_json`, `missing_platform`, `unknown_platform`) | | `router_upstream_requests_total` | `platform`, `outcome` | Upstream responses (`success`, `fail`, `error`) | | `router_upstream_duration_seconds` | `platform` | Upstream request latency histogram | Standard Go runtime and process metrics (`go_*`, `process_*`) are included automatically. ## Related Docs - Design spec: `spec.md` - Official push proxy: https://github.com/mattermost/mattermost-push-proxy - Mattermost push service docs: https://developers.mattermost.com/contribute/more-info/mobile/push-notifications/service/