Cloud Backend sends transactional email (user invitations, vendor assessment OTP, maintenance notifications) through Azure Communication Services (ACS) Email, authenticated with the app’s user-assigned managed identity. No API key is stored in Key Vault for this path.
In scope Out of scope Backend/internal/services/email.go and its consumersMarketing website contact form (still uses Resend via WebsiteContactApi/) Cloud + Worker Container Apps Multiple sender domains per environment (future) Verified domains cloud.monozu.io (prd) / cloud-dev.monozu.io (dev) Delivery status polling after 202 Accepted
Layer Path / resource Email client Backend/internal/services/email.goConfig COMMUNICATION_SERVICES_ENDPOINT, EMAIL_FROM_ADDR, AZURE_CLIENT_IDConsumers settings (invites), vendors (assessment invite + OTP), maintenance (window notifications), notifications dispatcher, weekly digestInfrastructure Infrastructure/infrastructure/modules/components/{emailService,communicationService}.bicepDNS automation Infrastructure/scripts/setup_acs_email_dns.py
Item Value REST endpoint POST {COMMUNICATION_SERVICES_ENDPOINT}/emails:send?api-version=2025-09-01Auth Bearer token from azidentity.ManagedIdentityCredential, scope https://communication.azure.com/.default Success HTTP 202 Accepted (fire-and-accept; no delivery polling) From (prd) noreply@cloud.monozu.ioFrom (dev) noreply@cloud-dev.monozu.io
Request body shape:
"senderAddress" : " noreply@cloud.monozu.io " ,
"to" : [{ "address" : " user@example.com " }],
"bcc" : [{ "address" : " other@example.com " }]
sequenceDiagram
participant Handler as Domain_Handler
participant Email as services.EmailService
participant Entra as Managed_Identity
participant ACS as ACS_Email
Handler->>Email: SendHTML / SendInvitation / ...
Email->>Entra: GetToken communication.azure.com
Entra-->>Email: Bearer token
Email->>ACS: POST emails:send
ACS-->>Email: 202 Accepted
Email-->>Handler: nil error
Symptom Likely cause Check Configured() == false locallyEmpty COMMUNICATION_SERVICES_ENDPOINT Expected without ACS in dev; set endpoint + run against Azure MI HTTP 401 from ACS UAMI missing Contributor on Communication Service resource communicationServiceContributorRole in apps.bicep; role propagation delayHTTP 4xx on send Domain not verified or not linked az communication email domain show; linkedDomains on Communication ServiceInvite succeeds in logs but no mail Fire-and-accept — ACS accepted but delivery failed downstream Azure Portal → Email → send logs; SPF/DKIM on Cloudflare Wrong From domain EMAIL_FROM_ADDR mismatch with verified domainBicep emailFromAddr + ACA plainEnv