Twilio
Overview
Twilio provides cloud communications APIs for SMS, MMS, voice calls, WhatsApp messaging, video, and phone number verification. The Twilio NuGet package offers a .NET SDK that wraps the Twilio REST API with strongly-typed resource classes. Each communication channel (SMS, voice, WhatsApp) uses the same MessageResource or CallResource pattern, differing only in the phone number format and configuration.
Twilio uses a webhook-based architecture: when a message arrives or a call connects, Twilio sends an HTTP request to your application with the event details. Your application responds with TwiML (Twilio Markup Language) to control the call flow or message response.
SDK Initialization and Configuration
Initialize the Twilio client with credentials from configuration, never hard-coded.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Twilio;
using Twilio.Clients;
public class TwilioSettings
{
public string AccountSid { get; set; } = string.Empty;
public string AuthToken { get; set; } = string.Empty;
public string FromPhoneNumber { get; set; } = string.Empty;
public string FromWhatsAppNumber { get; set; } = string.Empty;
}
// Registration in Program.cs
// builder.Services.Configure<TwilioSettings>(
// builder.Configuration.GetSection("Twilio"));
// builder.Services.AddSingleton<ITwilioRestClient>(sp =>
// {
// var settings = sp.GetRequiredService<IOptions<TwilioSettings>>().Value;
// return new TwilioRestClient(settings.AccountSid, settings.AuthToken);
// });
// builder.Services.AddTransient<SmsService>();
Sending SMS Messages
Send SMS using MessageResource.CreateAsync with the injected client.
using Microsoft.Extensions.Options;
using Twilio.Clients;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
using System.Threading.Tasks;
public class SmsService
{
private readonly ITwilioRestClient _client;
private readonly TwilioSettings _settings;
public SmsService(
ITwilioRestClient client,
IOptions<TwilioSettings> settings)
{
_client = client;
_settings = settings.Value;
}
public async Task<string> SendSmsAsync(
string toPhoneNumber, string body)
{
var message = await MessageResource.CreateAsync(
to: new PhoneNumber(toPhoneNumber),
from: new PhoneNumber(_settings.FromPhoneNumber),
body: body,
client: _client);
return message.Sid;
}
public async Task<string> SendMmsAsync(
string toPhoneNumber, string body, string mediaUrl)
{
var message = await MessageResource.CreateAsync(
to: new PhoneNumber(toPhoneNumber),
from: new PhoneNumber(_settings.FromPhoneNumber),
body: body,
mediaUrl: new List<Uri> { new Uri(mediaUrl) },
client: _client);
return message.Sid;
}
}
Making Voice Calls
Initiate outbound calls with TwiML instructions for what happens when the call connects.
using Microsoft.Extensions.Options;
using Twilio.Clients;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
using System;
using System.Threading.Tasks;
public class VoiceService
{
private readonly ITwilioRestClient _client;
private readonly TwilioSettings _settings;
public VoiceService(
ITwilioRestClient client,
IOptions<TwilioSettings> settings)
{
_client = client;
_settings = settings.Value;
}
public async Task<string> MakeCallAsync(
string toPhoneNumber, string twimlUrl)
{
var call = await CallResource.CreateAsync(
to: new PhoneNumber(toPhoneNumber),
from: new PhoneNumber(_settings.FromPhoneNumber),
url: new Uri(twimlUrl),
client: _client);
return call.Sid;
}
public async Task<string> MakeCallWithMessageAsync(
string toPhoneNumber, string message)
{
var twiml = new Twilio.TwiML.VoiceResponse()
.Say(message, voice: "Polly.Amy", language: "en-US")
.Pause(length: 1)
.Say("Goodbye.");
var call = await CallResource.CreateAsync(
to: new PhoneNumber(toPhoneNumber),
from: new PhoneNumber(_settings.FromPhoneNumber),
twiml: twiml.ToString(),
client: _client);
return call.Sid;
}
}
WhatsApp Messaging
Send WhatsApp messages using the whatsapp: prefix on phone numbers.
using Microsoft.Extensions.Options;
using Twilio.Clients;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
using System.Threading.Tasks;
public class WhatsAppService
{
private readonly ITwilioRestClient _client;
private readonly TwilioSettings _settings;
public WhatsAppService(
ITwilioRestClient client,
IOptions<TwilioSettings> settings)
{
_client = client;
_settings = settings.Value;
}
public async Task<string> SendWhatsAppAsync(
string toPhoneNumber, string body)
{
var message = await MessageResource.CreateAsync(
to: new PhoneNumber($"whatsapp:{toPhoneNumber}"),
from: new PhoneNumber(
$"whatsapp:{_settings.FromWhatsAppNumber}"),
body: body,
client: _client);
return message.Sid;
}
}
Phone Verification with Twilio Verify
Use Twilio Verify for OTP-based phone number verification.
using Twilio.Clients;
using Twilio.Rest.Verify.V2.Service;
using System.Threading.Tasks;
public class PhoneVerificationService
{
private readonly ITwilioRestClient _client;
private readonly string _verifyServiceSid;
public PhoneVerificationService(
ITwilioRestClient client, string verifyServiceSid)
{
_client = client;
_verifyServiceSid = verifyServiceSid;
}
public async Task<string> SendVerificationCodeAsync(
string phoneNumber)
{
var verification = await VerificationResource.CreateAsync(
to: phoneNumber,
channel: "sms",
pathServiceSid: _verifyServiceSid,
client: _client);
return verification.Status; // "pending"
}
public async Task<bool> CheckVerificationCodeAsync(
string phoneNumber, string code)
{
var check = await VerificationCheckResource.CreateAsync(
to: phoneNumber,
code: code,
pathServiceSid: _verifyServiceSid,
client: _client);
return check.Status == "approved";
}
}
Webhook Handling for Incoming Messages
Handle incoming SMS/WhatsApp messages via Twilio webhooks in ASP.NET Core.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Twilio.AspNet.Core;
using Twilio.TwiML;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/webhooks/sms", (
[FromForm] string From,
[FromForm] string Body) =>
{
var response = new MessagingResponse();
if (Body.Trim().Equals("STATUS", StringComparison.OrdinalIgnoreCase))
{
response.Message("Your order is being processed.");
}
else
{
response.Message(
$"Thanks for your message! We received: {Body}");
}
return Results.Content(
response.ToString(), "application/xml");
});
app.MapPost("/webhooks/voice", () =>
{
var response = new VoiceResponse();
response.Say("Welcome to our support line.", voice: "Polly.Amy");
response.Gather(
numDigits: 1,
action: new Uri("/webhooks/voice/menu", UriKind.Relative),
input: new List<Twilio.TwiML.Gather.InputEnum>
{
Twilio.TwiML.Gather.InputEnum.Dtmf
})
.Say("Press 1 for sales. Press 2 for support.");
return Results.Content(
response.ToString(), "application/xml");
});
app.Run();
Twilio Communication Channels
| Channel | From Format | To Format | Resource |
|---|---|---|---|
| SMS | +15551234567 | +15558675309 | MessageResource |
| MMS | +15551234567 | +15558675309 | MessageResource (with mediaUrl) |
whatsapp:+15551234567 | whatsapp:+15558675309 | MessageResource | |
| Voice | +15551234567 | +15558675309 | CallResource |
| Verify (OTP) | Managed by Twilio | +15558675309 | VerificationResource |
Best Practices
- •Store
AccountSidandAuthTokenin secure configuration (environment variables, Azure Key Vault, or user secrets) and inject via the options pattern; never hard-code credentials. - •Inject
ITwilioRestClientrather than callingTwilioClient.Init()globally, so services are testable and multiple Twilio accounts can coexist in the same application. - •Validate webhook requests using
Twilio.Security.RequestValidatorto verify that incoming HTTP requests are genuinely from Twilio, preventing spoofed webhook calls. - •Handle Twilio API exceptions by catching
Twilio.Exceptions.ApiExceptionand inspecting theCodeandMoreInfoproperties for actionable error details. - •Use Twilio Verify for phone number verification instead of building custom OTP logic; Verify handles rate limiting, fraud detection, and carrier lookup automatically.
- •Set status callbacks on
MessageResource.CreateAsyncvia thestatusCallbackparameter to receive delivery receipts and update message status in your database. - •Use E.164 format for all phone numbers (e.g.,
+15558675309) to ensure consistent behavior across countries and avoid formatting errors. - •Implement rate limiting on your side for user-triggered messages (e.g., OTP resend) to prevent abuse and unexpected Twilio charges; Twilio enforces its own limits but your costs still accrue.
- •Test with Twilio's test credentials (
ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXand test auth token) to validate integration without sending real messages or incurring charges. - •Monitor usage via the Twilio console or the
UsageRecordAPI to track costs, detect anomalies (sudden spike in messages), and set billing alerts to prevent budget overruns.