Marketing email (Azure Communication Services)
Newsletters, event announcements and prayer letters are sent from the Admin CRM through Azure Communication Services Email (replacing Mailchimp). Transactional mail uses a different path (Microsoft Graph).
| Code | campaigns/ (acs.py client, models.py, rendering.py, designs.py, ai_templates.py, audience.py, services.py, tasks.py, reporting.py, unsubscribe_views.py, webhook_views.py, events.py) |
| Runbook | campaigns/README.md (Event Grid setup, ops checklist for Azure portal + Cloudflare DNS, privacy) |
| Env | ACS_CONNECTION_STRING, ACS_SENDER_ADDRESS, ACS_SENDER_NAME, ACS_REPLY_TO, ACS_EVENTGRID_WEBHOOK_KEY, CAMPAIGN_SENDING_ENABLED, CAMPAIGN_SEND_ALLOWED_GROUP_IDS, CAMPAIGN_SEND_RATE_PER_MINUTE, CAMPAIGN_BATCH_SIZE, CAMPAIGN_LARGE_THRESHOLD, CAMPAIGN_REQUIRE_TEST_SEND, PUBLIC_BASE_URL, WEBSITE_BASE_URL, CAMPAIGN_POSTAL_ADDRESS, AI_TEMPLATE_ENABLED, AI_TEMPLATE_MODEL |
Lifecycle
- Template: an
EmailTemplatebuilt in the admin app's block designer, rich-text editor or raw HTML; optional AI draft (generate_ai_template_draft); images uploaded asEmailImage; variables such as{{ registration_url }}. - Audience: a pydantic
AudienceDefinition(groups, tags, regions/areas, saved map shapes, event proximity) previewed through/rest/campaigns/audience/preview/. - Prepare: recipients are materialised as
CampaignRecipient, suppression and marketing status applied, a test send is required ifCAMPAIGN_REQUIRE_TEST_SEND. - Send:
start_campaign→ chainedsend_campaign_batch(CAMPAIGN_BATCH_SIZEatCAMPAIGN_SEND_RATE_PER_MINUTE) →finalize_campaign. Large sends aboveCAMPAIGN_LARGE_THRESHOLDget an extra confirmation in the UI. - Events: ACS publishes delivery, bounce, open and click events to Event Grid, which calls
POST /rest/campaigns/acs-events/?key=<ACS_EVENTGRID_WEBHOOK_KEY>; they update recipients and the campaign report. - Unsubscribe: one-click link to
/unsubscribe/<token>/(outside/rest/), which sets the contact's marketing status.
Dev safety
The dev database holds real people. Two guards:
- Backend: sending is off unless
CAMPAIGN_SENDING_ENABLED=1. If it is on,CAMPAIGN_SEND_ALLOWED_GROUP_IDS(set to36, the "Email Testing" group, on dev) makes send/schedule refuse recipients outside those groups. - Admin CRM: against the dev backend it shows a banner and disables the send button unless the audience is limited to group 36 (
VITE_DEV_SEND_GROUP_IDS).
Keep both. Test with manage.py acs_send_test and the template send test action, which goes to an address you type.
Setup (once per environment)
In the Azure portal: an ACS resource with an Email Communication Service and a verified sender domain; DNS records (SPF, DKIM, DMARC) in Cloudflare; an Event Grid subscription on the ACS resource pointing at the webhook URL with the key. The ops checklist in the README walks through it.