Lyo.Sms.Twilio
1.0.2
dotnet add package Lyo.Sms.Twilio --version 1.0.2
NuGet\Install-Package Lyo.Sms.Twilio -Version 1.0.2
<PackageReference Include="Lyo.Sms.Twilio" Version="1.0.2" />
<PackageVersion Include="Lyo.Sms.Twilio" Version="1.0.2" />
<PackageReference Include="Lyo.Sms.Twilio" />
paket add Lyo.Sms.Twilio --version 1.0.2
#r "nuget: Lyo.Sms.Twilio, 1.0.2"
#:package Lyo.Sms.Twilio@1.0.2
#addin nuget:?package=Lyo.Sms.Twilio&version=1.0.2
#tool nuget:?package=Lyo.Sms.Twilio&version=1.0.2
Lyo.Sms.Twilio
A production-ready Twilio SMS/MMS service implementation for .NET, built on the extensible Lyo.Sms library.
Features
- Twilio Integration - Full support for Twilio SMS and MMS messaging
- Bulk Messaging - Efficient bulk SMS sending with rate limiting
- MMS Support - Send multimedia messages with up to 10 media attachments
- Message Querying - Query messages by various filter criteria
- Error Handling - Comprehensive error handling with Twilio-specific error codes
- Logging - Built-in logging support via Microsoft.Extensions.Logging
- Metrics - Optional metrics collection for monitoring SMS operations
- Dependency Injection - Full support for .NET dependency injection
- Async/Await - Fully asynchronous API with cancellation token support
- Thread-Safe - Thread-safe implementation for concurrent use
- Validation - Automatic validation of required configuration options
- Events - Events for message sending, message sent, bulk sending, and bulk sent
Examples
1. Configure Twilio Options
{
"TwilioOptions": {
"AccountSid": "your_account_sid",
"AuthToken": "your_auth_token",
"DefaultFromPhoneNumber": "+1234567890",
"BulkSmsConcurrencyLimit": 10,
"MaxMessageBodyLength": 1600,
"MaxBulkSmsLimit": 1000,
"EnableMetrics": false
}
}
1. Configure Twilio Options (2)
var options = new TwilioOptions
{
AccountSid = "your_account_sid",
AuthToken = "your_auth_token",
DefaultFromPhoneNumber = "+1234567890",
BulkSmsConcurrencyLimit = 10, // Max concurrent bulk SMS requests (default: 10)
MaxMessageBodyLength = 1600, // Max message body length in characters (default: 1600)
MaxBulkSmsLimit = 1000 // Max messages per bulk operation (default: 1000)
};
2. Register Services
// In ConfigureServices(context, services):
services.AddTwilioSmsServiceFromConfiguration(context.Configuration);
// Override the configuration section name (default: "TwilioOptions"):
// services.AddTwilioSmsServiceFromConfiguration(context.Configuration, "MyTwilio");
3. Use the Service
public class MyService
{
private readonly ISmsService _smsService;
public MyService(ISmsService smsService)
{
_smsService = smsService;
}
public async Task SendSmsAsync()
{
// Simple send
var result = await _smsService.SendSmsAsync(
to: "+1234567890",
body: "Hello, World!",
from: "+1987654321"
);
if (result.IsSuccess)
{
Console.WriteLine($"Message sent! ID: {result.MessageId}");
}
else
{
Console.WriteLine($"Failed: {result.ErrorMessage}");
}
}
}
Using the Builder Pattern
var builder = SmsMessageBuilder
.New()
.SetTo("+1234567890")
.SetFrom("+1987654321")
.SetBody("Hello, World!");
var result = await _smsService.SendAsync(builder);
Sending MMS (Multimedia Messages)
var builder = SmsMessageBuilder
.New()
.SetTo("+1234567890")
.SetFrom("+1987654321")
.SetBody("Check out this image!")
.AddMediaUrl(new Uri("https://example.com/image.jpg"));
var result = await _smsService.SendAsync(builder);
Sending Bulk Messages
var messages = new[]
{
SmsMessageBuilder.New().SetTo("+1111111111").SetBody("Message 1"),
SmsMessageBuilder.New().SetTo("+2222222222").SetBody("Message 2"),
SmsMessageBuilder.New().SetTo("+3333333333").SetBody("Message 3")
};
var results = await _smsService.SendBulkAsync(messages);
foreach (var result in results)
{
if (result.IsSuccess)
{
Console.WriteLine($"Sent to {result.To}: {result.MessageId}");
}
}
Sending Bulk Messages (2)
var bulkBuilder = BulkSmsBuilder
.New()
.SetDefaultFrom("+1987654321") // Optional: set default sender for all messages
.SetMaxLimit(100) // Optional: limit number of messages
.Add("+1111111111", "Message 1")
.Add("+2222222222", "Message 2")
.Add("+3333333333", "Message 3", "+19998887777"); // Override sender for specific message
var bulkResult = await _smsService.SendBulkAsync(bulkBuilder);
Console.WriteLine($"Total: {bulkResult.TotalCount}");
Console.WriteLine($"Success: {bulkResult.SuccessCount}");
Console.WriteLine($"Failed: {bulkResult.FailureCount}");
Console.WriteLine($"Elapsed: {bulkResult.ElapsedTime}");
if (bulkResult.IsCompleteSuccess)
{
Console.WriteLine("All messages sent successfully!");
}
foreach (var result in bulkResult.Results)
{
if (result.IsSuccess)
{
Console.WriteLine($"Sent to {result.To}: {result.MessageId}");
}
else
{
Console.WriteLine($"Failed to send to {result.To}: {result.ErrorMessage}");
}
}
Querying Messages
var filter = new SmsMessageQueryFilter
{
From = "+1987654321",
DateSentAfter = DateTime.UtcNow.AddDays(-7),
PageSize = 50
};
var result = await _smsService.GetMessagesAsync(filter);
foreach (var message in result.Items)
{
Console.WriteLine($"{message.DateSent}: {message.Body}");
}
// Cursor-based pagination: use result.NextCursor as DateSentBefore for next page when result.HasMore
Getting a Message by ID
var message = await _smsService.GetMessageByIdAsync("SM1234567890abcdef");
if (message.IsSuccess)
{
Console.WriteLine($"Status: {message.Status}");
Console.WriteLine($"Body: {message.Body}");
Console.WriteLine($"Price: {((TwilioSmsResult)message).Price} {((TwilioSmsResult)message).PriceUnit}");
}
Testing Connection
var isConnected = await _smsService.TestConnectionAsync();
if (isConnected)
{
Console.WriteLine("Connected to Twilio!");
}
Using Events
_smsService.MessageSending += (sender, args) =>
{
var request = args.SmsRequest;
Console.WriteLine($"Sending SMS to {request.To}: {request.Body}");
};
Using Events (2)
_smsService.MessageSent += (sender, args) =>
{
var result = args.SmsResult;
if (result.IsSuccess)
{
Console.WriteLine($"SMS sent successfully: {result.MessageId}");
if (result is TwilioSmsResult twilioResult)
{
Console.WriteLine($" Status: {twilioResult.Status}");
Console.WriteLine($" Price: {twilioResult.Price} {twilioResult.PriceUnit}");
}
}
else
{
Console.WriteLine($"SMS failed: {result.ErrorMessage}");
}
};
Using Events (3)
_smsService.BulkSending += (sender, args) =>
{
Console.WriteLine($"Starting bulk send for {args.BulkSmsMessage.Count} messages");
};
Using Events (4)
_smsService.BulkSent += (sender, args) =>
{
var bulkResult = args.BulkSmsResult;
Console.WriteLine($"Bulk send completed:");
Console.WriteLine($" Total: {bulkResult.TotalCount}");
Console.WriteLine($" Success: {bulkResult.SuccessCount}");
Console.WriteLine($" Failure: {bulkResult.FailureCount}");
Console.WriteLine($" Elapsed: {bulkResult.ElapsedTime}");
};
Using Events (5)
public class SmsNotificationService
{
private readonly ISmsService _smsService;
public SmsNotificationService(ISmsService smsService)
{
_smsService = smsService;
SubscribeToEvents();
}
private void SubscribeToEvents()
{
_smsService.MessageSending += OnMessageSending;
_smsService.MessageSent += OnMessageSent;
_smsService.BulkSending += OnBulkSending;
_smsService.BulkSent += OnBulkSent;
}
private void OnMessageSending(object? sender, SmsSendingEventArgs args)
{
Console.WriteLine($"Preparing to send SMS to {args.SmsRequest.To}");
}
private void OnMessageSent(object? sender, SmsSentEventArgs args)
{
if (args.SmsResult.IsSuccess)
{
Console.WriteLine($" SMS sent: {args.SmsResult.MessageId}");
if (args.SmsResult is TwilioSmsResult twilioResult)
{
Console.WriteLine($" Twilio Status: {twilioResult.Status}");
Console.WriteLine($" Cost: {twilioResult.Price} {twilioResult.PriceUnit}");
}
}
else
{
Console.WriteLine($" SMS failed: {args.SmsResult.ErrorMessage}");
}
}
private void OnBulkSending(object? sender, SmsBulkSendingEventArgs args)
{
Console.WriteLine($"Starting bulk SMS operation: {args.BulkSmsMessage.Count} messages");
}
private void OnBulkSent(object? sender, BulkSmsSentEventArgs args)
{
var bulkResult = args.BulkSmsResult;
Console.WriteLine($"Bulk SMS completed: {bulkResult.SuccessCount}/{bulkResult.TotalCount} successful in {bulkResult.ElapsedTime.TotalSeconds:F2}s");
if (bulkResult.FailureCount > 0)
{
Console.WriteLine($" Failures: {bulkResult.FailureCount}");
foreach (var r in bulkResult.FailedResults)
Console.WriteLine($" - {r.Data?.To}: {string.Join("; ", r.Errors?.Select(e => e.Message) ?? [])}");
}
}
}
TwilioSmsResult
var result = await _smsService.SendSmsAsync("+1234567890", "Hello");
if (result is TwilioSmsResult twilioResult)
{
Console.WriteLine($"Message SID: {twilioResult.MessageId}");
Console.WriteLine($"Status: {twilioResult.Status}");
Console.WriteLine($"Segments: {twilioResult.NumSegments}");
Console.WriteLine($"Price: {twilioResult.Price} {twilioResult.PriceUnit}");
Console.WriteLine($"Account SID: {twilioResult.AccountSid}");
}
Error Handling
var result = await _smsService.SendSmsAsync("+1234567890", "Hello");
if (!result.IsSuccess)
{
Console.WriteLine($"Error: {result.ErrorMessage}");
Console.WriteLine($"Error Code: {result.ErrorCode}");
if (result.Exception != null)
{
Console.WriteLine($"Exception: {result.Exception.Message}");
// Handle specific exception types
if (result.Exception is InvalidFormatException formatEx)
{
Console.WriteLine($"Invalid Value: {formatEx.InvalidValue}");
Console.WriteLine($"Valid Formats: {string.Join(", ", formatEx.ValidFormats)}");
}
}
}
1. Configure Twilio Options
Using Configuration File (appsettings.json)
Using Code
2. Register Services
Using Configuration Binding
AddTwilioSmsService(IConfiguration, string) is an alias kept for callers that prefer the shorter name;
both register the same singletons: TwilioOptions, TwilioOptionsValidator, TwilioSmsService, plus
the cross-typed ISmsService and ISmsService<TwilioSmsResult> interfaces backed by the same instance.
On net6.0+ targets an IHttpClient keyed "lyo-twilio-sms" is also registered so Twilio reuses the
shared IHttpClientFactory pool (resilient policies layered via the application's IHttpClientFactory
configuration apply automatically).
Sending Bulk Messages
Using IEnumerable of Builders
Using BulkSmsBuilder (Recommended)
Using Events
The Twilio SMS service provides events for monitoring message operations:
MessageSending Event
Fired before each message is sent (including during bulk operations):
MessageSent Event
Fired after each message is sent (success or failure):
BulkSending Event
Fired before a bulk send operation starts:
BulkSent Event
Fired after a bulk send operation completes:
Complete Event Example
Note: Events fire even when operations fail, allowing you to track all SMS operations regardless of success or failure. This is useful for monitoring, logging, and user notifications.
Twilio-Specific Features — Error Codes
Twilio-specific error codes are included in the result via TwilioSmsResult.TwilioErrorCode:
if (!result.IsSuccess && result is TwilioSmsResult twilioResult)
{
if (twilioResult.TwilioErrorCode.HasValue)
{
Console.WriteLine($"Twilio Error Code: {twilioResult.TwilioErrorCode}");
// Common error codes:
// 20003 - Unreachable destination handset
// 20429 - Too Many Requests (rate limit)
// 30001 - Queue overflow
// 30008 - Unknown destination handset
}
// The Errors collection (inherited from Result<SmsRequest>) carries human-readable messages and codes
var firstError = twilioResult.Errors?.FirstOrDefault();
Console.WriteLine($"Error: {firstError?.Message} ({firstError?.Code})");
}
Resilience
The library does not include built-in retry or timeout logic. Apply resilience at the application layer (e.g. using Lyo.Resilience with AddLyoResilienceHandler on the HttpClient, or by wrapping ISmsService calls) as needed.
Rate Limiting
- Concurrent Requests: Limited to 10 concurrent requests (configurable via
BulkSmsConcurrencyLimit) - Automatic Throttling: Built-in semaphore-based throttling
- Non-blocking: Uses async/await for efficient resource usage
- Bulk Limits: Maximum number of messages per bulk operation (configurable via
MaxBulkSmsLimit)
Thread Safety
- All instance fields are readonly
- Bulk operations use thread-safe collections (
ConcurrentBag) - Rate limiting uses
SemaphoreSlimfor thread-safe concurrency control - The underlying Twilio SDK client is thread-safe
TwilioOptions Properties
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
AccountSid |
string |
Yes | - | Your Twilio Account SID |
AuthToken |
string |
Yes | - | Your Twilio Auth Token |
DefaultFromPhoneNumber |
string? |
No | - | Default sender phone number |
BulkSmsConcurrencyLimit |
int |
No | 10 | Max concurrent bulk SMS requests |
MaxMessageBodyLength |
int |
No | 1600 | Max message body length in characters |
MaxBulkSmsLimit |
int |
No | 1000 | Max messages per bulk operation |
EnableMetrics |
bool |
No | false | Enable metrics collection |
Logging
The library uses Microsoft.Extensions.Logging for all logging:
services.AddLogging(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Information);
});
Log levels:
- Information: Successful operations, message details
- Warning: Retries, long messages, connection issues
- Error: Failures, exceptions
Phone numbers are automatically masked in logs (only last 4 digits shown) for privacy.
Metrics
Optional metrics collection is available:
services.AddLyoMetrics();
services.AddTwilioSmsService(options =>
{
options.EnableMetrics = true;
// ... other options
});
Metrics tracked:
sms.twilio.send.durationsms.twilio.send.successsms.twilio.send.failuresms.twilio.bulk.send.durationsms.twilio.bulk.send.totalsms.twilio.bulk.send.successsms.twilio.bulk.send.failuresms.twilio.bulk.send.last_duration_mssms.twilio.api.get_message.durationsms.twilio.api.get_messages.durationsms.twilio.test_connection.duration
Validation
AccountSidis requiredAuthTokenis required- Validation runs via
services.AddOptions<TwilioOptions>().ValidateOnStart()when usingAddTwilioSmsServiceFromConfiguration()/AddTwilioSmsService(IConfiguration, ...).
Dependencies
Generated from ProjectReference / PackageReference (same model as docs/Lyo.ProjectGraph.html).
Lyo.Exceptions— (direct, lyo)Lyo.Result— (direct, lyo)Lyo.Sms— (direct, lyo)Microsoft.Extensions.Http10.0.5— (direct, microsoft)Microsoft.Extensions.Logging.Abstractions10.0.5— (direct, microsoft)Microsoft.Extensions.Options.ConfigurationExtensions10.0.5— (direct, microsoft)Twilio7.14.9— (direct, third-party)Lyo.Common— (transitive, lyo)Lyo.Metrics— (transitive, lyo)Lyo.Sms.Models— (transitive, lyo)Microsoft.Extensions.DependencyInjection.Abstractions10.0.5— (transitive, microsoft)System.Memory4.6.3— (transitive, microsoft, netstandard2.0)System.Text.Json10.0.5— (transitive, microsoft, netstandard2.0)
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Lyo.Exceptions (>= 1.0.2)
- Lyo.Result (>= 1.0.2)
- Lyo.Sms (>= 1.0.2)
- Microsoft.Extensions.Http (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.5)
- Twilio (>= 7.14.9)
-
net10.0
- Lyo.Exceptions (>= 1.0.2)
- Lyo.Result (>= 1.0.2)
- Lyo.Sms (>= 1.0.2)
- Microsoft.Extensions.Http (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.5)
- Twilio (>= 7.14.9)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Lyo.Sms.Twilio:
| Package | Downloads |
|---|---|
|
Lyo.Sms.Twilio.Postgres
PostgreSQL implementation of Lyo.Sms.Twilio using Entity Framework Core. |
GitHub repositories
This package is not used by any popular GitHub repositories.