Playbook: MezmoExporterApiErrors — triage before touching the service key
Alert: MezmoExporterApiErrors (monitoring/prometheus/rules/pinbox24.yml)
Service: mezmo-exporter (monitoring-mezmo-exporter-1 on vps-i1, 217.154.82.162, port 9250)
Severity: warning
Occurrences to date: #4195, #4486, #4513 — all three transient upstream Mezmo blips, all self-resolved, zero actions required
Written after: #4513 (2026-07-28), which also recalibrated the alert so this class no longer pages
What the alert actually means
mezmo_exporter_api_errors_total increments on every failed Mezmo Export API query — 4xx, 5xx,
timeout, or a missing/invalid P24_INFRA_MEZMO_SERVICE_KEY. When it rises, every pinbox24_*_5m
metric collapses to 0, because _mezmo_query() fails soft and returns []. That collapse is why
the alert exists: without it, exporter breakage masquerades as a Pinbox24 log-forwarding outage via
Pinbox24NoLogs (this misdiagnosis cost ~20h on 2026-07-01/02, #2492).
The alert names P24_INFRA_MEZMO_SERVICE_KEY in its summary as a hint, not a diagnosis. Not one
of the three firings so far was a key problem. Do not rotate anything before running the triage below.
The counter advances per QUERY, not per poll cycle
collect_all() issues 12 _mezmo_query() calls per cycle (app.py:571–770): 1 unlabeled
mongo-timeout + 3 per backend container × 2 (v42-prod, v32-prod) + route-errors + log-volume +
slow-queries + mongoose + pm2-restarts + mailgun. At POLL_INTERVAL_S=60:
| Situation | Errors produced |
|---|---|
| One bad poll cycle | ~12 |
| Sustained breakage | ~12/min → ~180 per 15m |
This 12× multiplier is what made the original increase[15m] > 5, for: 0m rule fire on every
transient blip — one bad cycle already exceeded it. Since #4513 the rule is
increase(mezmo_exporter_api_errors_total[5m]) > 24 with for: 10m: the short window drains within
5 minutes of the last error, so for: genuinely gates on persistence and only a sustained failure
pages (at ~T+10 min). If you change POLL_INTERVAL_S or add/remove queries in collect_all(),
recompute the threshold as ~2 cycles’ worth of queries.
Triage — read-only, ~2 minutes
Everything here is read-only; none of it modifies the server.
1. Is it still failing, or already recovered?
ssh root@217.154.82.162 'docker logs --since 30m monitoring-mezmo-exporter-1 2>&1 | grep "Mezmo Export API"'Note the timestamp of the last error and the HTTP status. In all three occurrences so far
the burst had already ended before the alert was even filed — the increase() lookback window
simply still contained it.
2. Confirm data is flowing again
# On vps-i1 (or from any host that can reach the exporter):
curl -s http://localhost:9250/metrics | grep -E '^(mezmo_exporter_api_errors_total|pinbox24_log_lines_5m_total)'pinbox24_log_lines_5m_total > 0 means the exporter is querying Mezmo successfully right now.
Sample mezmo_exporter_api_errors_total twice ~90s apart — flat means fully recovered.
curl -s -G 'http://localhost:9090/api/v1/query' --data-urlencode 'query=increase(mezmo_exporter_api_errors_total[5m])'
curl -s 'http://localhost:9090/api/v1/alerts' | jq -r '.data.alerts[]? | select(.labels.alertname|test("Mezmo")) | "\(.labels.alertname) \(.state)"'3. Classify by HTTP status — this is the decision point
| Logged status | Meaning | Action |
|---|---|---|
5xx {"error":"Internal Server Error","code":"ServerError"} | Mezmo-side outage | None. Verify recovery, close the issue. (#4195, #4513) |
401 with "Unable to validate your servicekey at the moment" | Mezmo’s own token-validation backend blipping — note the wording “at the moment” | None. Transient; self-resolves in seconds. (#4486) |
| 401/403 sustained, and a direct probe also returns 401 | Key genuinely expired/revoked | Rotate — see below |
P4_INFRA_MEZMO_SERVICE_KEY not set in the logs | Key missing from the container env | Re-run secrets-sync.yml, then restart the exporter |
4. The distinguishing test — probe the key directly
Status code only. Never print the key value (see CLAUDE.md §Secrets).
KEY=$(grep -m1 '^P24_INFRA_MEZMO_SERVICE_KEY=' /opt/p24-infra/monitoring/.env | cut -d= -f2-)
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Token $KEY" https://api.mezmo.com/v1/config/view
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Token $KEY" https://api.mezmo.com/v1/config/keys
unset KEY- 200 → the key is valid. Whatever you saw was transient. Do not rotate.
- 401 → the key is dead. Proceed to rotation.
If the key really is dead
Rotation is a secret-manager operation — do not attempt SOPS writes from a sys-admin or
dev session. Follow docs/playbooks/secret-manager-request.md: file a [SECRET-REQUEST] issue and
dispatch it with job_type: secret-manager. The key lives in secrets/monitoring.env.sops as
P24_INFRA_MEZMO_SERVICE_KEY; secrets-sync.yml deploys it to vps-i1 and restarts the exporter.
Precedent for a genuine key break: PR #2596 (#2492) — the exporter’s auth header was malformed, the
metric sat at 0 for ~20h, and Pinbox24NoLogs misfired the whole time.
Related
monitoring/prometheus/rules/tests/mezmo_exporter_api_errors_test.yml— promtool unit tests pinning the calibration (transient bursts must not fire; sustained failure must)Pinbox24NoLogs— gated onincrease(mezmo_exporter_api_errors_total[10m]) == 0so exporter breakage can never masquerade as a log-forwarding outageMailgunPipelineExporterErrors— same pattern formailgun-pipeline-exporter, but correctly calibrated for its own 1-error-per-cycle, 300s-poll shape (> 2in 15m)