Issue: #669 (epic #651)
Component: FreeScout outbound email, Postmark relay, delivery monitor
Last reviewed: 2026-05-04
Flag: FLAG_POSTMARK_DELIVERY_MONITOR (default off)
FreeScout (at tickets.raxx.app) routes all outbound email through Postmark as
an SMTP relay. This avoids Lightsail's blocked port 25 + shared-IP reputation
problems. A delivery monitor webhook persists every Delivery/Bounce/SpamComplaint
event and alerts the operator via Slack DM when threshold rates are exceeded.
These steps are performed in the FreeScout admin UI, not via code. Do this once
after the Postmark Server is provisioned and POSTMARK_SERVER_API_KEY is in vault.
The token is stored in Infisical vault at:
path: /MooseQuest/postmark/
key: POSTMARK_SERVER_API_KEY
env: prod
Do NOT use POSTMARK_ACCOUNT_API_KEY — that is the account-level key and cannot
be used as an SMTP credential.
https://tickets.raxx.app/ (CF Access gate — use kris@moosequest.net).| Field | Value |
|---|---|
| SMTP Host | smtp.postmarkapp.com |
| SMTP Port | 587 |
| Encryption | STARTTLS |
| SMTP Username | (the POSTMARK_SERVER_API_KEY value from vault) |
| SMTP Password | (same as username — Postmark uses the token as both) |
| From Name | Raxx Support |
| From Email | support@raxx.app |
Authentication-Results header).raxx.app domain signature should show all three statuses green:
- SPF: Verified
- DKIM: Verified
- Return-Path: VerifiedIf any show red, the DNS records from PR #676 may not have propagated yet. Wait 30 minutes and refresh.
The delivery monitor receives Postmark events via webhook. Set up the webhook in the Postmark dashboard after the code (PR #669) is deployed.
Generate a random secret to authenticate Postmark deliveries:
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
heroku config:set POSTMARK_DELIVERY_WEBHOOK_SECRET=<generated-value> \
--app raxx-api-prod >/dev/null 2>&1
Also set it on staging:
heroku config:set POSTMARK_DELIVERY_WEBHOOK_SECRET=<same-value> \
--app raxx-api-staging >/dev/null 2>&1
Store the value in vault:
path: /MooseQuest/postmark/
key: POSTMARK_DELIVERY_WEBHOOK_SECRET
env: prod
| Field | Value |
|---|---|
| Webhook URL | https://api.raxx.app/webhooks/postmark/delivery |
| Auth header | X-Postmark-Webhook-Token: <value from step 2.1> |
| Events | Delivery, Bounce, SpamComplaint |
HTTP 401 because the flag is still off.
That is correct; once the flag is on, it will return HTTP 200.After deployment and webhook secret is set:
heroku config:set FLAG_POSTMARK_DELIVERY_MONITOR=1 \
--app raxx-api-prod >/dev/null 2>&1
Test the webhook in Postmark dashboard again — it should now return HTTP 200.
When SLACK_BOT_TOKEN is set, the delivery monitor sends operator DMs when
bounce or spam complaint rates exceed thresholds.
| Threshold | Rate | Window |
|---|---|---|
| Bounce alert | >1% | Last 1 hour |
| Spam alert | >0.1% | Last 24 hours |
To enable:
# Get the bot token from vault at /MooseQuest/slack/SLACK_BOT_TOKEN
heroku config:set SLACK_BOT_TOKEN=<xoxb-token> --app raxx-api-prod >/dev/null 2>&1
The DM channel defaults to D0AJ7K184TV (operator DM). Override:
heroku config:set SLACK_OPERATOR_DM_CHANNEL=D0AJ7K184TV \
--app raxx-api-prod >/dev/null 2>&1
Note: Slack alerts are non-fatal. A misconfigured or absent Slack token never blocks the webhook response or the mail flow.
Once the flag is on, the recent-deliveries endpoint is available at:
GET https://api.raxx.app/api/_internal/postmark/recent-deliveries
Response shape:
{
"events": [
{
"id": 42,
"event_type": "Delivery",
"message_id": "abc-123",
"recipient": "user@example.com",
"tag": "support-outbound",
"bounce_type": null,
"bounce_description": null,
"recorded_at": "2026-05-04 12:34:56"
}
],
"counts_24h": {
"delivered": 150,
"bounced": 2,
"spam_complaint": 0
},
"total_24h": 152
}
This endpoint is behind CF Access (api.raxx.app) — no additional auth
required for operator access.
When the Postmark sender-token adapter (#950, velvet_postmark_adapter flag) is
stable:
POSTMARK_SERVER_API_KEY rotation will be managed by Velvet.Until #950 is ready, rotation is manual:
/MooseQuest/postmark/POSTMARK_SERVER_API_KEY.The delivery monitor is designed to feed the console alerts bell (#789) once that component's alert-source API is stable. Until #789 is fully shipped:
SLACK_BOT_TOKEN is configured.When #789 lands, update _check_alert_thresholds() in
backend_v2/api/routes/postmark_delivery.py to also POST to
/api/_internal/alerts/sources/postmark.
POSTMARK_DELIVERY_WEBHOOK_SECRET is not set or does not match the
value configured in the Postmark dashboard webhook.heroku config:get POSTMARK_DELIVERY_WEBHOOK_SECRET --app raxx-api-prodFLAG_POSTMARK_DELIVERY_MONITOR is off.heroku config:get FLAG_POSTMARK_DELIVERY_MONITOR --app raxx-api-prodheroku config:set FLAG_POSTMARK_DELIVERY_MONITOR=1 --app raxx-api-prod >/dev/null 2>&1dig TXT raxx.app — must include include:spf.mtasv.net.dig TXT pm._domainkey.raxx.app — must return the
Postmark DKIM public key.bash
ssh -i /tmp/lightsail_us_east_1.pem admin@54.146.13.200 \
sudo tail -50 /var/log/apache2/freescout-error.logbash
ssh -i /tmp/lightsail_us_east_1.pem admin@54.146.13.200 \
"curl -v telnet://smtp.postmarkapp.com:587" 2>&1 | head -20| Key | Path | Env |
|---|---|---|
POSTMARK_SERVER_API_KEY |
/MooseQuest/postmark/ |
prod |
POSTMARK_DELIVERY_WEBHOOK_SECRET |
/MooseQuest/postmark/ |
prod |
POSTMARK_ACCOUNT_API_KEY |
/MooseQuest/postmark/ |
prod |
SLACK_BOT_TOKEN |
/MooseQuest/slack/ |
prod |
| Variable | Required | Description |
|---|---|---|
POSTMARK_DELIVERY_WEBHOOK_SECRET |
Yes (for flag on) | Shared token from Postmark webhook config |
FLAG_POSTMARK_DELIVERY_MONITOR |
Yes (must be 1) |
Feature flag to enable endpoints |
SLACK_BOT_TOKEN |
Optional | Slack bot token for delivery alerts |
SLACK_OPERATOR_DM_CHANNEL |
Optional | DM channel (default D0AJ7K184TV) |