Files
print_forge/site/directmail
ai_ml_operations 8a97e3fbe2 Initial commit
2026-09-06 04:27:41 -07:00
..
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00
2026-09-06 04:27:41 -07:00

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=<SMTP2GO_WEBHOOK_SECRET> 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).

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 PCMs 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).

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=<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_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/<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.