Skip to main content

Sending SMS

POST /fhir/R4/Communication/$send-sms-twilio

Pass the Communication resource inline in the request body. The operation creates a new Communication in Medplum after sending.

You can also invoke instance-level on an existing resource:

POST /fhir/R4/Communication/<id>/$send-sms-twilio

Request Body​

{
"resourceType": "Communication",
"status": "preparation",
"medium": [{
"coding": [{
"system": "https://terminology.hl7.org/CodeSystem/v3-ParticipationMode",
"code": "SMSWRIT"
}]
}],
"extension": [
{ "url": "https://medplum.com/twilio-to-number", "valueString": "+15005550001" },
{ "url": "https://medplum.com/twilio-from-number", "valueString": "+15005550006" } // only needed if TWILIO_FROM_NUMBER project secret not set
],
"payload": [{ "contentString": "Your appointment is tomorrow at 10am." }]
}

Communication Fields​

FieldRequirementNotes
mediumRequiredMust include SMSWRIT code (https://terminology.hl7.org/CodeSystem/v3-ParticipationMode, code SMSWRIT)
payloadRequiredMust contain at least one entry with contentString
extension (twilio-to-number)RequiredRecipient phone number in E.164 format
extension (twilio-from-number)OptionalSending phone number; falls back to TWILIO_FROM_NUMBER project secret
senderOptionalPassed through unchanged — the integration does not validate or auto-populate this field
recipientOptionalRecommended for FHIR completeness but not required to send

See Phone Number Formats below for accepted input formats.

What the Operation Does​

  1. Validates the Communication (SMSWRIT medium, non-empty payload)
  2. Reads the to-number from the twilio-to-number extension
  3. Resolves the from-number from the twilio-from-number extension, falling back to the TWILIO_FROM_NUMBER project secret
  4. Calls the Twilio Messages API
  5. Saves the Communication with the Twilio MessageSid as an identifier, status = in-progress, the outbound direction category, and both phone number extensions

Phone Number Formats​

All phone numbers (to-number, from-number, and TWILIO_FROM_NUMBER) are normalized to E.164 before use. The following input formats are all accepted:

InputNormalized
+15005550006+15005550006 (already E.164, returned as-is)
15005550006+15005550006 (11-digit with country code)
5005550006+15005550006 (10-digit US number)
500-555-0006+15005550006 (dashes stripped)
(500) 555-0006+15005550006 (parentheses and spaces stripped)

Normalization strips all non-digit, non-plus characters first, then applies the rules above.

Delivery Status​

Once Twilio confirms delivery, the integration updates Communication.status on the outbound resource automatically:

Twilio StatusFHIR Communication.status
queued, sending, sent, acceptedin-progress
delivered, readcompleted
failed, undelivered, canceledstopped

Terminal states (completed, stopped) are never overwritten by intermediate ones, protecting against out-of-order Twilio callbacks.

Stored Communication Resource​

After sending, the stored outbound Communication looks like:

{
"resourceType": "Communication",
"status": "in-progress",
"identifier": [{ "system": "https://www.twilio.com/", "value": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }],
"category": [{
"coding": [{ "system": "https://medplum.com/twilio-sms-direction", "code": "outbound" }]
}],
"medium": [{
"coding": [{ "system": "https://terminology.hl7.org/CodeSystem/v3-ParticipationMode", "code": "SMSWRIT" }]
}],
"extension": [
{ "url": "https://medplum.com/twilio-from-number", "valueString": "+15005550006" },
{ "url": "https://medplum.com/twilio-to-number", "valueString": "+15005550001" }
],
"sent": "2025-01-15T10:30:00Z",
"payload": [{ "contentString": "Your appointment is tomorrow at 10am." }]
}