# Messaging Campaign compose/send, SMTP2GO email + SMS, PCM Integrations postcards, and delivery webhooks. ## SMTP2GO webhook setup Campaign report page polls provider events every 10s. Create **one** SMTP2GO webhook (email + SMS share a URL — paid plans cap at 10 webhooks). ### Auth (`SMTP2GO_WEBHOOK_SECRET`) 1. Set `SMTP2GO_WEBHOOK_SECRET` in `.env` / prod env (long random string). 2. In SMTP2GO, set **Authorization header** to **Bearer** and paste that same secret (do not leave it as “None”). 3. Fallback: `?token=` on the webhook URL also works. ### Unified email + SMS webhook | Field | Value | |-------|--------| | URL | `https://mkdrealtor.com/portal/messaging/webhooks/smtp2go/` | | Authorization header | **Bearer** + `SMTP2GO_WEBHOOK_SECRET` | | Output type | JSON | | Users | email SMTP user(s) **and** the SMS API key (`SMTP2GO_SMS_API_KEY`) | | Email events | processed, bounced, rejected, spam, delivered, unsub/resub, opened, clicked | | Email headers | `X-Monica-Message-Id` | | SMS events | Submitted, Sending, Delivered, Failed, Rejected (and Opt-out if shown) | The handler classifies each POST from the payload (`sms_*` / `destination_number` → SMS; `rcpt` / `email_id` / `X-Monica-Message-Id` → email; inbound `text=STOP` without `event` → SMS opt-out). `X-Monica-Message-Id` is set on every campaign email send and is required so email webhook events match the correct recipient row. Invalid / missing header values no longer 500 the endpoint (SMTP2GO “Test this webhook” often sends a sample non-UUID). ## Campaign tracked links (UTM + piha.li) Composer auto-inserts a homepage link: | Param | Value | |-------|--------| | `utm_source` | `monica` | | `utm_medium` | `email` / `sms` / `postcard` | | `utm_campaign` | hyphenated campaign name | When `SHORTENER_BASE_URL` + `SHORTENER_API_TOKEN` are set, the app POSTs that long HTTPS URL to `url_shortening_service` (`POST /api/links/`, Bearer `monica:`) and puts `short_url` (`https://piha.li/`) in SMS, email hrefs, and postcard QR codes. Empty env → long UTM URL (local default). Mint against the **API host**, not `piha.li`. See `.env.example` / `.env.prod.example`. SMS correlation uses `message_id` (SMS id), then `destination_number` phone fallback. Do **not** treat webhook `id` as the SMS id. **Opt-out:** SMTP2GO auto-handles replies `STOP` / `UNSUB` / `UNSUBSCRIBE`. This endpoint also accepts inbound POSTs without an `event` field (`text=STOP`, `from=…`) and opts the contact out of SMS. Later sends to opted-out numbers are typically `sms_rejected`. Beta / other hosts: swap the hostname, keep the path. Legacy aliases (same handler): `/portal/messaging/webhooks/email/` and `/portal/messaging/webhooks/sms/` — prefer `/smtp2go/` for new configs. ## PCM Integrations (postcards) Default postcard provider. Designer embeds PCM’s editor; orders use DirectMail API v3. ### Env | Var | Purpose | |-----|---------| | `PCM_API_KEY` | API key from PCM portal (My Account → API Keys) | | `PCM_API_SECRET` | Matching API secret; used with key on `POST /auth/login` | | `PCM_CHILD_REF_NBR` | Optional child-app ref for multi-account | | `PCM_WEBHOOK_SECRETS` | Comma-separated signature secrets (one per PCM subscription) | | `PCM_WEBHOOK_SECRET` | Optional single-secret alias (merged into the list above) | | `PCM_RETURN_ADDRESS` | JSON return address on orders | | `POSTCARD_PROVIDER` | `pcm` (default) | Auth flow: `POST /auth/login` with `{apiKey, apiSecret}` → short-lived `token` used as `Authorization: Bearer …` on design/order calls ([PCM Logging In](https://docs.pcmintegrations.com/docs/directmail-api/ffef03a112bb0-logging-in)). ### Designer Portal → **Postcard design**: create/list designs via API, edit in iframe (`POST /design/custom`, `GET /design/{id}/edit?mode=embed`). Save as a `MessageTemplate` (stores `design_id`) then pick it when composing a postcard campaign. ### Postcard webhook PCM allows **one event per subscription**, and each subscription gets its own **signature secret** (copy-only in the UI). Create one subscription per status you care about; point them all at the same URL and paste every secret into env. | Field | Value | |-------|--------| | URL | `https://mkdrealtor.com/portal/messaging/webhooks/postcard/` | | Events | One subscription each: Pending, Processing, Processed, Delivered, Undeliverable, Canceled (skip QrCodeScan unless needed) | | Environments | Sandbox and/or Production as needed | | Secrets | Copy each subscription signature → `PCM_WEBHOOK_SECRETS=sec1,sec2,…` | We accept Bearer, `?token=`, or common signature headers (raw secret or HMAC-SHA256 of body) matching **any** listed secret. Correlation: we send `extRefNbr=` on each recipient; webhooks should echo that (or `orderID`, matched to `Message.provider_message_id`). ### Campaign completion email When a campaign reaches **completed** (email, SMS, or postcard), one summary email goes to `campaign.created_by.email`, else `CONTACT_EMAIL`. Guarded by `Campaign.notify_sent_at` so it only sends once. ### Local development SMTP2GO / PCM cannot reach `localhost`. Use a tunnel (Cloudflare Tunnel / ngrok) to `:8000`, or test webhooks against beta/prod. For real SMTP delivery locally (not console logs): ```bash # in .env EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend EMAIL_HOST_USER=… EMAIL_HOST_PASSWORD=… SMTP2GO_WEBHOOK_SECRET=… SMTP2GO_SMS_API_KEY=… # SMS sends only PCM_API_KEY=… PCM_API_SECRET=… PCM_WEBHOOK_SECRETS=sec1,sec2,… PCM_RETURN_ADDRESS={…} ``` ### Endpoints (app) | Path | Purpose | |------|---------| | `POST /portal/messaging/webhooks/smtp2go/` | Unified SMTP2GO email + SMS (+ inbound STOP) | | `POST /portal/messaging/webhooks/email/` | Legacy alias → same as `/smtp2go/` | | `POST /portal/messaging/webhooks/sms/` | Legacy alias → same as `/smtp2go/` | | `POST /portal/messaging/webhooks/postcard/` | PCM order / mail tracking events | | `GET /portal/messaging/campaigns//status.json` | Live stats for the campaign report UI | | `GET /portal/messaging/postcard/` | PCM designer iframe | Code: `webhooks.py`, `providers/postcard/pcm.py`, `views.py`.