Skip to content

SMS Messaging

Miranda supports SMS as a communication channel alongside email. SMS messages are sent via Twilio and can be used in broadcast campaigns and automated sequence steps.

The SmsService handles all outbound SMS:

class SmsService
{
public function send(
Contact $contact,
string $body,
Foundation $foundation,
?Campaign $campaign = null,
?SequenceStep $sequenceStep = null,
): void;
}
  1. Opt-in check — Verifies communication_preferences.sms_opt_in is truthy. If not, the send is silently skipped with a log message.
  2. Phone check — Verifies the contact has a phone number. If not, skipped.
  3. Merge fields — Replaces first_name, last_name, and foundation_name placeholders (wrapped in double curly braces).
  4. Delivery — Sends via Twilio REST API using the configured from number.
  5. Communication log — Creates a CommunicationLog record with channel sms, direction outbound, and the first 500 characters as body_preview.
  6. Activity — Records ActivityType::SmsSent on the contact’s timeline.

If Twilio delivery fails, the CommunicationLog status is set to failed and the error is logged. The send does not throw — it fails gracefully to avoid disrupting batch operations.

SMS opt-in is strictly enforced at two levels:

  1. SmsService — Checks communication_preferences.sms_opt_in before every send
  2. CampaignService — Auto-adds sms_opt_in: true to segment criteria for SMS campaigns

Contacts without sms_opt_in: true in their communication_preferences will never receive SMS messages, regardless of how they are targeted.

SMS requires Twilio credentials in .env:

TWILIO_SID=AC...
TWILIO_TOKEN=...
TWILIO_FROM=+15551234567

The from number is configured at config('services.twilio.from').

Create a campaign with channel: "sms":

{
"name": "Fundraiser Reminder",
"body": "Hi {{first_name}}, our gala is this Saturday! We'd love to see you there. - {{foundation_name}}",
"channel": "sms",
"segment_criteria": {
"tags": ["gala-invitee"],
"sms_opt_in": true
}
}

The CampaignService handles batching and delivery identically to email campaigns, using SmsService instead of EmailService.

Add an SMS step to a sequence:

{
"position": 3,
"channel": "sms",
"body": "Hi {{first_name}}, just checking in. Thank you for your support of {{foundation_name}}!",
"delay_hours": 168
}

The subject field is ignored for SMS steps.

  • first_name — Contact’s first name
  • last_name — Contact’s last name
  • foundation_name — Foundation’s name

Wrap each placeholder in double curly braces in your message body.

PlanSMS per month
StarterNot available
Growth500
Pro2,000

Every SMS creates a CommunicationLog record:

FieldValue
channelsms
directionoutbound
subjectnull
body_previewFirst 500 characters of the merged message
statussent or failed
campaign_idCampaign ID (if from a campaign)
sequence_step_idSequence step ID (if from a sequence)

In the testing environment, SMS messages are logged instead of sent via Twilio. This allows integration tests to verify message content without incurring Twilio charges.