Shoppers can register, save shipping details, and view order history while cards stay on Stripe. EasyPost tracker updates (including numbers from Pirate Ship) and 1–5 star reviews are limited to buyers. The contact form now only asks for email and a message.
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)
- Set
SMTP2GO_WEBHOOK_SECRETin.env/ prod env (long random string). - In SMTP2GO, set Authorization header to Bearer and paste that same secret (do not leave it as “None”).
- 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 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).
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.