Twilio
Twilio is RoundTrip's planned SMS delivery provider. It will be used to send text message notifications to technicians and other stakeholders — job assignments, status updates, reminders, and other operational messages that need to reach people in the field without requiring them to be in the app.
:::info Not Yet Implemented
Twilio integration is planned but no application code has been written yet. The Twilio account is created and credentials are stored in Key Vault, but the ISmsService interface and TwilioSmsService implementation do not yet exist in the codebase. This page documents the plan and configuration so the integration can be built when the time comes.
:::
Planned Use Cases
| Notification | Trigger | Recipient | Status |
|---|---|---|---|
| Job assigned | Ticket assigned to technician | Technician | Planned |
| Job updated | Ticket status or details changed | Technician | Planned |
| Job reminder | Upcoming job within X hours | Technician | Planned |
| On my way | Technician marks "En Route" | Client | Planned |
| Job completed | Ticket marked Complete | Client, Dispatcher | Planned |
| Invoice sent | Invoice emailed to client | Dispatcher confirmation | Planned |
Configuration
Credentials are already stored in Key Vault awaiting implementation.
Key Vault Secrets
| Secret Name | Config Key | Purpose |
|---|---|---|
Twilio--AccountSid | Twilio:AccountSid | Twilio account identifier |
Twilio--AuthToken | Twilio:AuthToken | Authentication token for API calls |
Twilio--FromNumber | Twilio:FromNumber | Sending phone number (Twilio number) |
App Service Settings
| Setting Name | Value |
|---|---|
Twilio__AccountSid | @Microsoft.KeyVault(VaultName=kv-roundtrip-production;SecretName=Twilio--AccountSid) |
Twilio__AuthToken | @Microsoft.KeyVault(VaultName=kv-roundtrip-production;SecretName=Twilio--AuthToken) |
Twilio__FromNumber | @Microsoft.KeyVault(VaultName=kv-roundtrip-production;SecretName=Twilio--FromNumber) |
Twilio Dashboard
| Item | Detail |
|---|---|
| Login | Bitwarden → "Twilio" |
| Account SID | Found on Twilio Console home page |
| Phone Numbers | Phone Numbers → Manage → Active Numbers |
| Message Logs | Monitor → Logs → Messaging |
Implementation Plan
When the time comes to implement Twilio, follow this pattern consistent with the rest of the Infrastructure layer:
1. Define the interface in Core
// RoundTrip.API.Core/Interfaces/ISmsService.cs
public interface ISmsService
{
Task SendAsync(
string toPhoneNumber,
string message,
CancellationToken cancellationToken = default);
}
2. Implement in Infrastructure
// RoundTrip.API.Infrastructure/Services/TwilioSmsService.cs
public sealed class TwilioSmsService : ISmsService
{
private readonly TwilioRestClient _client;
private readonly string _fromNumber;
private readonly ILogger<TwilioSmsService> _logger;
public TwilioSmsService(IConfiguration configuration, ILogger<TwilioSmsService> logger)
{
_logger = logger;
var accountSid = configuration["Twilio:AccountSid"]
?? throw new InvalidOperationException("Twilio:AccountSid is not configured.");
var authToken = configuration["Twilio:AuthToken"]
?? throw new InvalidOperationException("Twilio:AuthToken is not configured.");
_fromNumber = configuration["Twilio:FromNumber"]
?? throw new InvalidOperationException("Twilio:FromNumber is not configured.");
_client = new TwilioRestClient(accountSid, authToken);
}
public async Task SendAsync(string toPhoneNumber, string message, CancellationToken ct)
{
_logger.LogInformation("Sending SMS to {PhoneNumber}", toPhoneNumber);
var messageResource = await MessageResource.CreateAsync(
to: new PhoneNumber(toPhoneNumber),
from: new PhoneNumber(_fromNumber),
body: message,
client: _client);
_logger.LogInformation(
"SMS sent to {PhoneNumber}. SID: {Sid} Status: {Status}",
toPhoneNumber, messageResource.Sid, messageResource.Status);
}
}
3. Register in DI
// InfrastructureServiceExtensions.cs
services.AddScoped<ISmsService, TwilioSmsService>();
4. Use via Hangfire job (not inline)
Like all external delivery, SMS must be sent from a Hangfire background job — never inline in a request handler:
// In a domain event handler:
_backgroundJobClient.Enqueue<SendSmsJob>(
job => job.ExecuteAsync(technicianPhone, message));
Design Considerations
When building the Twilio integration, consider:
Opt-out handling — technicians and clients should be able to opt out of SMS notifications. Store SMS opt-out preference on TenantUser and Client and check before enqueuing any SMS job.
Phone number validation — validate phone numbers in E.164 format (+15551234567) before storing. Use a value object PhoneNumber in the domain layer.
Message length — standard SMS is 160 characters. Messages over 160 characters are split and billed as multiple segments. Keep notification messages concise.
International numbers — Twilio supports international SMS but requires a number capable of sending to the destination country. Consider this when tenants have clients or technicians outside the US.
Rate limiting — Twilio imposes rate limits per sending number. For high-volume scenarios, consider a messaging service (pool of numbers) rather than a single number.