Files
monica_site/site/messaging/README.md
T
westfarn 1f7d78de64
Deploy Beta / unit-tests (push) Successful in 9s
Deploy Beta / docker (push) Successful in 17s
Deploy Beta / deploy-beta (push) Successful in 2m31s
Add Django site, Docker packaging, and beta/prod Gitea deploys.
Unignore site/ (was blocked by mkdocs /site rule), add compose/Docker/uv tooling, and split deploys so push to main goes to beta while prod stays manual.
2026-08-08 07:32:55 -05:00

4.0 KiB
Raw Blame History

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 two webhooks in SMTP2GO → Settings → Webhooks (email and SMS stay separate).

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=<SMTP2GO_WEBHOOK_SECRET> on the webhook URL also works.

Email webhook

Field Value
URL https://mkdrealtor.com/portal/messaging/webhooks/email/
Authorization header Bearer + SMTP2GO_WEBHOOK_SECRET
Output type JSON
Email events processed, bounced, rejected, spam, delivered, unsub/resub, opened, clicked
Email headers X-Monica-Message-Id
SMS events leave unchecked

X-Monica-Message-Id is set on every campaign email send and is required so webhook events match the correct recipient row.

Beta / other hosts: swap the hostname, keep the path.

SMS webhook (separate)

Field Value
URL https://mkdrealtor.com/portal/messaging/webhooks/sms/
Authorization header Bearer + same SMTP2GO_WEBHOOK_SECRET
Output type JSON
Email events leave unchecked
SMS events Submitted, Sending, Delivered, Failed, Rejected, Opt-out

This endpoint also accepts inbound reply POSTs (text=STOP, from=…) and opts the contact out of SMS.

PCM Integrations (postcards)

Default postcard provider. Designer embeds PCMs editor; orders use DirectMail API v3.

Env

Var Purpose
PCM_API_KEY Bearer token for https://v3.pcmintegrations.com
PCM_WEBHOOK_SECRET Auth for inbound status webhooks
PCM_RETURN_ADDRESS JSON return address on orders
POSTCARD_PROVIDER pcm (default)

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

Create a webhook subscription in the PCM dashboard (Working with Webhooks):

Field Value
URL https://mkdrealtor.com/portal/messaging/webhooks/postcard/
Authorization Bearer + PCM_WEBHOOK_SECRET
Events Order / recipient status (Pending, Processing, Processed, Delivered, Undeliverable, Canceled)
Environments Sandbox and/or Production as needed

Fallback: ?token=<PCM_WEBHOOK_SECRET> on the URL.

Correlation: we send extRefNbr=<Message.uuid> 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):

# 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_WEBHOOK_SECRET=PCM_RETURN_ADDRESS={}

Endpoints (app)

Path Purpose
POST /portal/messaging/webhooks/email/ Email delivery / open / click / bounce / …
POST /portal/messaging/webhooks/sms/ SMS delivery events + inbound STOP
POST /portal/messaging/webhooks/postcard/ PCM order / mail tracking events
GET /portal/messaging/campaigns/<id>/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.