Skip to content

SMS Providers

FORGE supports sending SMS messages and OTP verification through multiple provider drivers. The SMS system is used for phone number verification, two-factor authentication, and transactional notifications.

SmsProvider Trait

All SMS drivers implement the SmsProvider trait:

rust
#[async_trait]
pub trait SmsProvider: Send + Sync {
    /// Send a plain text SMS message
    async fn send(&self, to: &str, message: &str) -> Result<(), SmsError>;

    /// Send a one-time password via the provider's verification service
    async fn send_otp(&self, to: &str, code: &str) -> Result<(), SmsError>;

    /// Verify an OTP code entered by the user
    async fn verify_otp(&self, to: &str, code: &str) -> Result<bool, SmsError>;
}

Slack Integration (Development/Testing)

When SMS_SLACK_ENABLED=true, all SMS messages are intercepted and sent to a Slack channel instead of the actual provider. This is useful for:

  • Development: See OTP codes without needing a real phone
  • Testing: Verify SMS content in your Slack workspace
  • Monitoring: Track all SMS traffic in a dedicated channel
SMS_SLACK_ENABLED=true
┌─────────┐    ┌───────────────┐    ┌─────────────┐
│ App     │───▶│ SlackSmsProxy │───▶│ Slack       │
│ sends   │    │ (intercepts)  │    │ Channel     │
│ SMS     │    └───────────────┘    └─────────────┘
└─────────┘

SMS_SLACK_ENABLED=false (default)
┌─────────┐    ┌─────────────────┐    ┌───────────────┐
│ App     │───▶│ Twilio/Unifonic │───▶│ Real SMS      │
│ sends   │    │ Provider        │    │ to Phone      │
│ SMS     │    └─────────────────┘    └───────────────┘
└─────────┘

Configuration:

VariableTypeDefaultRequiredDescription
SMS_SLACK_ENABLEDbooleanfalseNoSend SMS to Slack instead of provider
SMS_SLACK_WEBHOOK_URLstring-Yes*Slack incoming webhook URL
SMS_SLACK_CHANNELstring-NoOptional channel override

*Required when SMS_SLACK_ENABLED=true. The app will fail at startup if missing.

Setup:

  1. Create an Incoming Webhook in your Slack workspace
  2. Set the environment variables:
bash
SMS_SLACK_ENABLED=true
SMS_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX
SMS_SLACK_CHANNEL=#sms-logs  # Optional
  1. SMS messages will now appear in Slack:
*SMS Message*
:iphone: To: `+1234567890`
:envelope: Message:
Your verification code is: 123456
:id: ID: `uuid-here`

WARNING

The Slack proxy cannot verify OTPs - verification must be done via your database. This is the same behavior as the log driver.

Available Drivers

Slack (Development)

Send SMS/OTP messages to a Slack channel instead of real phones. Perfect for development and testing without SMS costs.

Installation:

bash
forge new --interactive
# Select "Slack (development)" when prompted for SMS provider

Or via CLI:

bash
forge new myapp --sms slack

Configuration:

VariableTypeRequiredDescription
SMS_DRIVERstringYesSet to slack
SMS_SLACK_WEBHOOK_URLstringYesSlack incoming webhook URL
SMS_SLACK_CHANNELstringNoOptional channel override

Setup:

  1. Create an Incoming Webhook in your Slack workspace
  2. Configure your .env:
bash
SMS_DRIVER=slack
SMS_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX
SMS_SLACK_CHANNEL=#otp-codes  # Optional
  1. OTP codes will appear in Slack:
📱 SMS to +1234567890
Your verification code is: 123456. This code expires in 10 minutes.

TIP

Use Slack for development to avoid SMS costs and see OTP codes instantly in your team channel.

WARNING

The Slack provider cannot verify OTPs server-side - verification is done via your database (same as Log driver).

Twilio

Full-featured SMS and OTP provider using Twilio's REST API and Verify service.

Installation:

bash
forge provider:add sms:twilio

Configuration:

SettingTypeDescription
twilio_account_sidstringTwilio Account SID
twilio_auth_tokenencryptedTwilio Auth Token
twilio_from_numberstringSender phone number (E.164 format)
twilio_verify_sidstringTwilio Verify Service SID

TIP

The twilio_verify_sid is required for OTP functionality. Create a Verify Service in the Twilio Console to obtain this value.

How OTP works with Twilio:

1. send_otp()  → Calls Twilio Verify API to send code
2. User enters code in your app
3. verify_otp() → Calls Twilio Verify API to check code
4. Returns true/false

Unifonic

SMS provider popular in the Middle East and North Africa region.

Installation:

bash
forge provider:add sms:unifonic

Configuration:

SettingTypeDescription
unifonic_app_sidencryptedUnifonic Application SID
unifonic_sender_idstringSender ID (alphanumeric or number)

TIP

Unifonic sender IDs may require registration with local telecom authorities depending on the destination country.

Vonage

Global SMS provider (formerly Nexmo).

Installation:

bash
forge provider:add sms:vonage

Configuration:

SettingTypeDescription
vonage_api_keystringVonage API Key
vonage_api_secretencryptedVonage API Secret
vonage_fromstringSender ID or number

Usage

Creating the Provider

The factory creates the correct driver based on your configuration:

rust
use crate::services::sms::SmsFactory;

let sms = SmsFactory::create(&settings).await?;

Sending a Message

rust
// Send a plain text SMS
sms.send("+1234567890", "Your order has been shipped!").await?;

OTP Verification

rust
// Step 1: Send an OTP to the user's phone
sms.send_otp("+1234567890", "123456").await?;

// Step 2: User enters the code in your app

// Step 3: Verify the code
let is_valid = sms.verify_otp("+1234567890", "123456").await?;

if is_valid {
    // Phone number verified
} else {
    // Invalid code
}

WARNING

Phone numbers must be in E.164 format (e.g., +1234567890). Sending to numbers without the country code prefix will fail with most providers.

Configuration via Admin

All SMS settings are managed through the Settings admin page under the SMS group. Credentials are stored as encrypted settings and masked in the admin interface.

Settings > SMS
┌──────────────────────────────────────────────────┐
│  Provider:         [Twilio           ▼]          │
│  Account SID:      [ACxxxxxxxxxx       ]         │
│  Auth Token:       [••••••••••••••••   ]         │
│  From Number:      [+15551234567       ]         │
│  Verify SID:       [VAxxxxxxxxxx       ]         │
└──────────────────────────────────────────────────┘

Error Handling

All SMS operations return Result<(), SmsError> with structured error types:

rust
match sms.send("+1234567890", "Hello").await {
    Ok(()) => println!("Message sent"),
    Err(SmsError::InvalidNumber) => println!("Bad phone number format"),
    Err(SmsError::ProviderError(msg)) => println!("Provider error: {}", msg),
    Err(SmsError::RateLimited) => println!("Too many requests"),
    Err(e) => println!("Unexpected error: {}", e),
}

Released under the MIT License.