Skip to main content

Reaching People: Email, SMS, and Phone

Once the alert lifecycle has decided who to notify, something has to actually reach them. That's three services over Azure Communication Services — email, SMS, and outbound phone calls. Two of them come wired up by one registration call; the third doesn't, and that asymmetry is the thing to remember.

One call gives you two of the three​

AddCommunicationServices registers the SmsClient and EmailClient (singletons, built from the CommunicationServicesUrl), then ISmsService and IEmailService. Inject either and send:

  • IEmailService.SendEmailsAsync — subject/body to a list of recipients, with optional CC/BCC, an optional htmlBody (falls back to HTML-encoded plain text), and attachments. Two overloads take either EntraUserEntity or the SQL User model, so callers on either side of the data layer can use it directly.
  • ISmsService.SendBulkAsync — one message to many phone numbers.
Gotcha: SMS silently chunks at 100 recipients

The Azure SDK accepts at most 100 messages per send. SendBulkAsync checks the count and, above 100, splits the list into hundred-number chunks before sending:

if (phoneNumbers.Count() <= 100) { /* single send */ }
else { var chunks = CommunicationServicesUtilities.ChunkOversizedListByHundreds(phoneNumbers); /* … */ }

This is handled for you — but it means a 250-recipient blast is three SDK calls, not one. If you're rate-limited or partially failing on large sends, the chunk boundary is where to look; a failure in chunk two doesn't roll back chunk one.

The third channel you have to wire yourself​

PhoneCallService places outbound calls via the ACS CallAutomationClient, with Cognitive Services for text-to-speech. It is not registered by AddCommunicationServices — if you inject IPhoneCallService without registering it yourself, you get a DI resolution error, not a silent no-op.

Gotcha: phone calls are opt-in, and callbacks need a reachable URL

CallTargetAsync(phoneNumber, hostAppCallbackUri) starts a call, and ACS posts call events back to hostAppCallbackUri — the host app then uses GetCallConnection(callEvent) to drive the in-progress call (play text-to-speech, hang up). That callback URL must be publicly reachable by Azure; a localhost URL means the call connects and then nothing further happens, because the events can't be delivered.

Wrapping up​

Three outbound channels ride on Azure Communication Services. AddCommunicationServices wires email and SMS; phone calls are opt-in because they're stateful and need a reachable callback plus Cognitive Services. The one operational surprise is SMS auto-chunking at 100 recipients — convenient, but it turns one logical blast into several independent sends.

Next, the services that guard the doors — API keys, tokens, and encryption: Guarding the Doors.