> For the complete documentation index, see [llms.txt](https://docs.flexbase.in/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flexbase.in/data-and-providers/providers/text-message.md).

# Text Message

## Description

Message providers in FlexBase expose a unified interface (`IFlexTextMessageProvider`) for sending SMS and notifications across providers like Twilio, Firebase, and AWS SNS.

Your application code should depend on `IFlexTextMessageProvider`.

## Important concepts

* **`IFlexTextMessageProvider` is the contract**: send via `SendSmsAsync(...)`, `SendPushAsync(...)`, or `SendAsync(...)`.
* **Provider bridge**: generated infrastructure commonly registers `IFlexTextMessageProviderBridge` (which also implements `IFlexTextMessageProvider`) so behavior can be overridden safely.
* **Channel support is provider-specific**: use `SupportedChannels` / `SupportsChannel(...)` to validate capability.

## Configuration in DI

Only register the provider—Flex auto-wires generated PostBus handlers/plugins that consume the interface.

```csharp
public static class OtherApplicationServicesConfig
{
	public static IServiceCollection AddOtherApplicationServices(
		this IServiceCollection services,
		IConfiguration configuration)
	{
		// Pick ONE (or register multiple with different compositions).
		services.AddFlexConsoleMessageProvider(configuration);
		// services.AddFlexTwilioMessageProvider(configuration);
		// services.AddFlexFirebaseMessageProvider(configuration);
		// services.AddFlexAwsSnsMessageProvider(configuration);
		return services;
	}
}
```

## appsettings.json

Message provider configuration is read from `FlexBase:Providers:Message:<Provider>`.

```json
{
    "FlexBase": {
	"Providers": {
	  "Message": {
		"Console": {
		  "DefaultChannel": "Sms",
		  "DefaultSenderId": "MYAPP",
		  "SimulateDelay": true,
		  "SimulatedDelay": "00:00:00.500"
		},
		"Twilio": {
		  "AccountSid": "<store-in-secrets>",
		  "AuthToken": "<store-in-secrets>",
		  "DefaultFromNumber": "+15551234567",
		  "MessagingServiceSid": null,
		  "MaxRetries": 3,
		  "Timeout": "00:00:30"
		},
		"Firebase": {
		  "ProjectId": "<your-project-id>",
		  "CredentialsJsonPath": null,
		  "CredentialsJson": null,
		  "DefaultTopic": null,
		  "MaxRetries": 3,
		  "Timeout": "00:00:30"
		},
		"AwsSns": {
		  "AccessKeyId": "<store-in-secrets>",
		  "SecretAccessKey": "<store-in-secrets>",
		  "Region": "us-east-1",
		  "DefaultSenderId": "MYAPP",
		  "DefaultTopicArn": null,
		  "SmsType": "Transactional",
		  "MaxRetries": 3,
		  "Timeout": "00:00:30"
		}
	  }
	}
    }
}
```

## Examples (template-based)

This mirrors the generated PostBus handler template shape. You do **not** register the handler manually.

```csharp
using Microsoft.Extensions.Logging;
using Sumeru.Flex;
using System.Threading.Tasks;

namespace {YourApplication}.PostBusHandlers.Message;

public partial class SendSmsHandler : ISendSmsHandler
{
	protected string EventCondition = string.Empty;

	protected readonly ILogger<SendSmsHandler> _logger;
	protected readonly IFlexHost _flexHost;
	protected readonly IFlexTextMessageProvider _messageProvider;

	protected FlexAppContextBridge? _flexAppContext;

	public SendSmsHandler(
		ILogger<SendSmsHandler> logger,
		IFlexHost flexHost,
		IFlexTextMessageProvider messageProvider)
	{
		_logger = logger;
		_flexHost = flexHost;
		_messageProvider = messageProvider;
	}

	public virtual async Task Execute(SendSmsCommand cmd, IFlexServiceBusContext serviceBusContext)
	{
		_flexAppContext = cmd.Dto.GetAppContext();  //do not remove this line

		var result = await _messageProvider.SendSmsAsync(
			phoneNumber: cmd.Dto.PhoneNumber,
			content: cmd.Dto.Content,
			senderId: cmd.Dto.SenderId);

		cmd.Dto.MessageId = result.MessageId;
		cmd.Dto.IsSuccess = result.IsSuccess;

		await this.Fire(EventCondition, serviceBusContext);
	}
}
```

## Provider considerations

* Validate channel support via `SupportsChannel(...)` before sending.
* Keep secrets (Twilio/AuthToken, AWS keys, Firebase credentials) out of source control. \["action"] = "verify", \["userId"] = 12345 }, Metadata = new Dictionary\<string, string> // Tracking metadata { \["campaign"] = "signup", \["source"] = "web" } };

  var result = await \_messageProvider.SendAsync(message); }

````

### Templated Messages

```csharp
public async Task SendTemplatedSmsAsync(string phoneNumber, string userName)
{
    await _messageProvider.SendTemplatedAsync(
        recipient: phoneNumber,
        templateId: "order_shipped",
        templateVariables: new Dictionary<string, string>
        {
            ["userName"] = userName,
            ["orderNumber"] = "ORD-12345",
            ["carrier"] = "FedEx",
            ["trackingUrl"] = "https://track.example.com/123"
        },
        channel: FlexMessageChannel.Sms
    );
}
````

### Bulk Messaging

```csharp
// Send same message to multiple recipients
public async Task SendBulkSmsAsync(List<string> phoneNumbers, string message)
{
    var result = await _messageProvider.SendToManyAsync(
        recipients: phoneNumbers,
        content: message,
        channel: FlexMessageChannel.Sms
    );

    _logger.LogInformation(
        "Sent SMS to {SuccessCount}/{TotalCount} recipients",
        result.SuccessCount,
        result.TotalCount
    );

    // Log failures
    foreach (var failure in result.Results.Where(r => !r.IsSuccess))
    {
        _logger.LogWarning(
            "Failed to send to {Recipient}: {Error}",
            failure.Recipient,
            failure.ErrorMessage
        );
    }
}

// Send different messages
public async Task SendPersonalizedMessagesAsync(List<User> users)
{
    var messages = users.Select(user => new FlexMessage
    {
        Recipients = new List<string> { user.PhoneNumber },
        Content = $"Hello {user.Name}, your order is ready!",
        Channel = FlexMessageChannel.Sms
    });

    var result = await _messageProvider.SendBulkAsync(messages);
}
```

### Slack/Teams Integration

```csharp
// Slack message
public async Task SendSlackNotificationAsync(string channel, string message)
{
    var slackMessage = new FlexMessage
    {
        Recipients = new List<string> { channel },  // "#general" or "U1234567890"
        Content = "Deployment to production completed successfully! ✅",
        Channel = FlexMessageChannel.Slack,
        Data = new Dictionary<string, object>
        {
            ["blocks"] = new[]
            {
                new { type = "header", text = new { type = "plain_text", text = "Deployment Complete" } },
                new { type = "section", text = new { type = "mrkdwn", text = "*Version:* 1.2.3\n*Environment:* Production" } }
            }
        }
    };

    await _messageProvider.SendAsync(slackMessage);
}

// Teams message
public async Task SendTeamsNotificationAsync(string channelId, string message)
{
    var teamsMessage = new FlexMessage
    {
        Recipients = new List<string> { channelId },
        Title = "Build Failed",
        Content = "The build for branch `main` has failed.",
        Channel = FlexMessageChannel.Teams,
        Priority = FlexMessagePriority.Urgent
    };

    await _messageProvider.SendAsync(teamsMessage);
}
```

## Key Points to Consider

### Best Practices

1. **Validate Recipients** - Use `ValidateRecipient()` before sending
2. **Handle Delivery Status** - Check `IsSuccess` and log failures
3. **Use Console in Dev** - Avoid sending real messages during development
4. **Set Priorities** - Use `High` priority for time-sensitive messages
5. **Include Expiry** - Set `ExpiresAt` for time-sensitive content
6. **Handle Rate Limits** - Implement retry logic for `RATE_LIMITED` errors
7. **Monitor Costs** - Track `Cost` in results for paid providers

### Validation

```csharp
public async Task<bool> SendIfValidAsync(string phoneNumber, string message)
{
    if (!_messageProvider.ValidateRecipient(phoneNumber, FlexMessageChannel.Sms))
    {
        _logger.LogWarning("Invalid phone number: {PhoneNumber}", phoneNumber);
        return false;
    }

    var result = await _messageProvider.SendSmsAsync(phoneNumber, message);
    return result.IsSuccess;
}

// Check channel support
public async Task SendToSupportedChannelAsync(string recipient, string message)
{
    if (_messageProvider.SupportsChannel(FlexMessageChannel.WhatsApp))
    {
        await _messageProvider.SendAsync(recipient, message, FlexMessageChannel.WhatsApp);
    }
    else
    {
        // Fallback to SMS
        await _messageProvider.SendSmsAsync(recipient, message);
    }
}
```

### Error Handling

```csharp
public async Task<bool> SendWithRetryAsync(FlexMessage message, int maxRetries = 3)
{
    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            var result = await _messageProvider.SendAsync(message);
            
            if (result.IsSuccess)
            {
                return true;
            }

            switch (result.ErrorCode)
            {
                case "INVALID_RECIPIENT":
                    _logger.LogError("Invalid recipient, not retrying");
                    return false;
                
                case "RATE_LIMITED":
                    _logger.LogWarning("Rate limited, waiting before retry");
                    await Task.Delay(TimeSpan.FromSeconds(5 * attempt));
                    break;
                
                case "INSUFFICIENT_FUNDS":
                    _logger.LogError("Insufficient account balance");
                    return false;
                
                default:
                    _logger.LogWarning(
                        "Send failed (attempt {Attempt}/{Max}): {Error}",
                        attempt, maxRetries, result.ErrorMessage
                    );
                    break;
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unexpected error sending message");
            
            if (attempt == maxRetries)
                throw;
            
            await Task.Delay(TimeSpan.FromSeconds(2 * attempt));
        }
    }

    return false;
}
```

### Testing Connection

```csharp
// In startup/health check
public async Task<bool> VerifyMessageServiceAsync()
{
    var isConnected = await _messageProvider.TestConnectionAsync();
    
    if (!isConnected)
    {
        _logger.LogError("Message provider is not configured correctly");
        return false;
    }

    _logger.LogInformation(
        "Message provider connected: {Provider}, Channels: {Channels}",
        _messageProvider.ProviderName,
        string.Join(", ", _messageProvider.SupportedChannels)
    );
    
    return true;
}
```

## Examples

### Complete Notification Service

```csharp
public class NotificationService : INotificationService
{
    private readonly IFlexTextMessageProvider _messageProvider;
    private readonly ILogger<NotificationService> _logger;

    public NotificationService(
        IFlexTextMessageProvider messageProvider,
        ILogger<NotificationService> logger)
    {
        _messageProvider = messageProvider;
        _logger = logger;
    }

    public async Task<bool> SendVerificationCodeAsync(
        string phoneNumber,
        string code)
    {
        try
        {
            var result = await _messageProvider.SendSmsAsync(
                phoneNumber: phoneNumber,
                content: $"Your verification code is: {code}. Valid for 10 minutes.",
                senderId: "MYAPP"
            );

            if (result.IsSuccess)
            {
                _logger.LogInformation(
                    "Verification code sent to {PhoneNumber} (MessageId: {MessageId})",
                    phoneNumber, result.MessageId
                );
                return true;
            }

            _logger.LogError(
                "Failed to send verification code to {PhoneNumber}: {Error}",
                phoneNumber, result.ErrorMessage
            );
            return false;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error sending verification code to {PhoneNumber}", phoneNumber);
            return false;
        }
    }

    public async Task SendOrderNotificationAsync(Order order)
    {
        var message = $"Your order #{order.Id} has been confirmed. Total: ${order.Total:F2}";

        // Send SMS if phone number available
        if (!string.IsNullOrEmpty(order.User.PhoneNumber))
        {
            await _messageProvider.SendSmsAsync(order.User.PhoneNumber, message);
        }

        // Send push notification if device token available
        if (!string.IsNullOrEmpty(order.User.DeviceToken))
        {
            await _messageProvider.SendPushAsync(
                order.User.DeviceToken,
                "Order Confirmed",
                message,
                new Dictionary<string, object>
                {
                    ["orderId"] = order.Id,
                    ["action"] = "view_order"
                }
            );
        }
    }
}
```

### Multi-Channel Alert System

```csharp
public class AlertService
{
    private readonly IFlexTextMessageProvider _messageProvider;

	public AlertService(IFlexTextMessageProvider messageProvider)
		=> _messageProvider = messageProvider;

    public async Task SendCriticalAlertAsync(string title, string message)
    {
        var tasks = new List<Task>();

        // SMS to on-call engineers
        tasks.Add(_messageProvider.SendToManyAsync(
            GetOnCallPhoneNumbers(),
            $"[CRITICAL] {title}: {message}",
            FlexMessageChannel.Sms
        ));

        // Slack notification
        tasks.Add(_messageProvider.SendAsync(
            "#alerts",
            $"🚨 *{title}*\n{message}",
            FlexMessageChannel.Slack
        ));

        // Teams notification
        tasks.Add(_messageProvider.SendAsync(
            GetTeamsChannelId(),
            message,
            FlexMessageChannel.Teams
        ));

        await Task.WhenAll(tasks);
    }
}
```

## Testing

```csharp
public class MessageServiceTests
{
    [Fact]
    public async Task SendSmsAsync_WithValidNumber_Succeeds()
    {
        // Arrange
        var provider = new FlexConsoleTextMessageProvider(
            Options.Create(new FlexConsoleMessageOptions())
        );

        // Act
        var result = await provider.SendSmsAsync(
            "+1234567890",
            "Test message"
        );

        // Assert
        Assert.True(result.IsSuccess);
        Assert.NotNull(result.MessageId);
    }

    [Fact]
    public void ValidateRecipient_WithInvalidNumber_ReturnsFalse()
    {
        // Arrange
        var provider = new FlexConsoleTextMessageProvider(
            Options.Create(new FlexConsoleMessageOptions())
        );

        // Act
        var isValid = provider.ValidateRecipient("invalid", FlexMessageChannel.Sms);

        // Assert
        Assert.False(isValid);
    }
}
```

## See Also

* [Email Providers](/data-and-providers/providers/e-mail.md) - Email sending
* [FlexMessage Provider Reference](https://github.com/sumeru-flexbase/flexbase-docs/blob/main/ReferenceDocs/README_FlexMessageProvider.md) - Detailed API documentation
