Raxx · internal docs

internal · gated ↑ index

FreeScout Postmark SMTP relay — operator runbook

Issue: #669 (epic #651) Component: FreeScout outbound email, Postmark relay, delivery monitor Last reviewed: 2026-05-04 Flag: FLAG_POSTMARK_DELIVERY_MONITOR (default off)


Overview

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.


Part 1 — FreeScout SMTP configuration (operator UI)

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.

Step 1.1 — Retrieve the Postmark server token

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.

Step 1.2 — Configure FreeScout SMTP outbound

  1. Sign into https://tickets.raxx.app/ (CF Access gate — use kris@moosequest.net).
  2. Navigate to Admin → Manage → Settings → General.
  3. Scroll to the Email Sending (SMTP) section.
  4. Set:
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
  1. Click Save.
  2. Use the Send Test Email button on the same page. - Enter your own email address and click send. - Within 30 seconds, verify the email arrives in your inbox with correct DKIM/SPF pass markers (check "Show original" in Gmail to see the Authentication-Results header).

Step 1.3 — Verify DKIM/SPF in Postmark dashboard

  1. In Postmark: Servers → raxx → Settings → Signatures.
  2. The raxx.app domain signature should show all three statuses green: - SPF: Verified - DKIM: Verified - Return-Path: Verified

If any show red, the DNS records from PR #676 may not have propagated yet. Wait 30 minutes and refresh.


Part 2 — Postmark delivery webhook setup

The delivery monitor receives Postmark events via webhook. Set up the webhook in the Postmark dashboard after the code (PR #669) is deployed.

Step 2.1 — Generate the webhook secret

Generate a random secret to authenticate Postmark deliveries:

python3 -c "import secrets; print(secrets.token_urlsafe(32))"

Step 2.2 — Set the secret in Heroku

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

Step 2.3 — Configure the webhook in Postmark

  1. In Postmark: Servers → (your server) → Settings → Outbound → Webhooks.
  2. Click Add webhook.
  3. Set:
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
  1. Click Test — you should see HTTP 401 because the flag is still off. That is correct; once the flag is on, it will return HTTP 200.

Step 2.4 — Enable the feature flag

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.


Part 3 — Slack DM alerts (optional)

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.


Part 4 — Recent deliveries API

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.


Part 5 — Token rotation via Velvet (#950)

When the Postmark sender-token adapter (#950, velvet_postmark_adapter flag) is stable:

  1. The POSTMARK_SERVER_API_KEY rotation will be managed by Velvet.
  2. After Velvet rotates the key, the operator must update FreeScout's SMTP password via the admin UI (Part 1, step 1.2) with the new token value.
  3. This manual UI step will be automated once a FreeScout API adapter lands (tracked in a follow-up card).

Until #950 is ready, rotation is manual:

  1. Generate a new Postmark Server API token in the Postmark dashboard.
  2. Update FreeScout SMTP password (Part 1, step 1.2).
  3. Update vault at /MooseQuest/postmark/POSTMARK_SERVER_API_KEY.
  4. Revoke the old token in the Postmark dashboard.

Part 6 — Alerts bell (#789) integration

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:

When #789 lands, update _check_alert_thresholds() in backend_v2/api/routes/postmark_delivery.py to also POST to /api/_internal/alerts/sources/postmark.


Troubleshooting

Webhook returns 401

Webhook returns 404

Outbound emails going to spam

  1. Check Postmark dashboard — Servers → Activity — for delivery failures.
  2. Check recent-deliveries endpoint for Bounce / SpamComplaint events.
  3. Verify DKIM/SPF are green in Postmark (Part 1, step 1.3).
  4. Check SPF record: dig TXT raxx.app — must include include:spf.mtasv.net.
  5. Check DKIM record: dig TXT pm._domainkey.raxx.app — must return the Postmark DKIM public key.

FreeScout test email fails

  1. Confirm SMTP settings match Part 1, step 1.2.
  2. Check FreeScout error logs on Lightsail: bash ssh -i /tmp/lightsail_us_east_1.pem admin@54.146.13.200 \ sudo tail -50 /var/log/apache2/freescout-error.log
  3. Test SMTP connectivity from Lightsail to Postmark: bash ssh -i /tmp/lightsail_us_east_1.pem admin@54.146.13.200 \ "curl -v telnet://smtp.postmarkapp.com:587" 2>&1 | head -20

Vault paths summary

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

Environment variables summary

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)