Twilio SMS Provider
🎯 Overview
The SMS Provider (Providers/sms-api.js) handles all SMS messaging operations through Twilio, including single and bulk SMS sending, delivery status tracking, message detail retrieval, and SMS pricing information.
Source: external/Integrations/Twilio/Providers/sms-api.js
Key Capabilities:
- Send single SMS messages
- Send bulk SMS to multiple recipients
- Retrieve message details by SID
- Get SMS pricing by country
- Status callback support for delivery tracking
🔌 Provider Functions
sendSms()
Send SMS messages to multiple recipients in bulk.
Signature:
exports.sendSms = async(accountSID, authToken, recipients, from, contents, statusCallback);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
accountSID | String | ✅ | Twilio Account SID or Subaccount SID |
authToken | String | ✅ | Twilio Auth Token |
recipients | Array | ✅ | Array of phone numbers (E.164 format) |
from | String | ✅ | Sender phone number (must be Twilio-owned) |
contents | String | ✅ | SMS message content |
statusCallback | String | ❌ | Webhook URL for delivery status updates |
Returns:
Promise<{
validNumberArr: string[]; // Successfully validated numbers
invalidNumberArr: string[]; // Invalid phone numbers
sentSmsDetails: Array<
| {
// Details of sent messages
status: 'fulfilled';
value: {
number: string; // Recipient number
data: {
// Twilio message object
sid: string; // Message SID
status: string; // queued, sent, delivered, failed
to: string;
from: string;
body: string;
dateCreated: Date;
dateSent: Date;
price: string;
priceUnit: string;
};
};
}
| {
status: 'rejected';
reason: {
number: string;
error: Error; // Twilio error object
};
}
>;
}>;
Business Logic:
-
Create Twilio Client
- Creates client with provided accountSID and authToken
- Client is instantiated per request (no connection pooling)
-
Send SMS to Each Recipient
- Iterates through recipients array
- Calls
sendSingleSms()for each number - Uses
Promise.allSettled()to handle parallel sending
-
Categorize Results
- Successful sends →
sentSmsDetailsarray - Fulfilled promises →
validNumberArr - Rejected promises →
invalidNumberArr
- Successful sends →
Example Usage:
const smsProvider = require('./Providers/sms-api');
// Send bulk SMS campaign
const result = await smsProvider.sendSms(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN,
['+12125551234', '+12125555678', '+19175559876'],
'+12125550100', // From number
'Your appointment is confirmed for tomorrow at 2pm.',
'https://app.dashclicks.com/webhooks/sms-status', // Status callback
);
console.log('Sent to:', result.validNumberArr);
// Output: ['+12125551234', '+12125555678', '+19175559876']
console.log('Failed:', result.invalidNumberArr);
// Output: []
// Process sent messages
result.sentSmsDetails.forEach(detail => {
if (detail.status === 'fulfilled') {
console.log(`Message to ${detail.value.number}: ${detail.value.data.sid}`);
console.log(`Status: ${detail.value.data.status}`);
} else {
console.error(`Failed to ${detail.reason.number}:`, detail.reason.error.message);
}
});
Data Flow:
sequenceDiagram
participant API as DashClicks API
participant Provider as SMS Provider
participant Twilio as Twilio SDK
participant Recipients
API->>Provider: sendSms(accountSID, recipients, message)
Provider->>Provider: Create Twilio client
loop For each recipient
Provider->>Provider: sendSingleSms(number)
Provider->>Twilio: messages.create()
Twilio->>Recipients: Send SMS
Twilio-->>Provider: Message SID + status
end
Provider->>Provider: Categorize results
Note over Provider: validNumberArr<br/>invalidNumberArr<br/>sentSmsDetails
Provider-->>API: Return results
alt Status Callback URL provided
Recipients-->>Twilio: Delivery confirmation
Twilio->>API: POST /webhooks/sms-status
Note over API: status: delivered/failed/undelivered
end
style Provider fill:#e3f2fd
style Twilio fill:#fff4e6
sendSingleSms()
Send a single SMS message to one recipient.
Signature:
exports.sendSingleSms = async(accountSID, authToken, contents, from, number, statusCallback);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
accountSID | String | ✅ | Twilio Account SID |
authToken | String | ✅ | Twilio Auth Token |
contents | String | ✅ | SMS message content |
from | String | ✅ | Sender phone number |
number | String | ✅ | Recipient phone number (E.164 format) |
statusCallback | String | ❌ | Webhook URL for status updates |
Returns:
Promise<{
number: string;
data: TwilioMessageInstance; // Twilio message object
}>;
Business Logic:
-
Create Twilio Client
- Instantiates client with account credentials
- Client is scoped to this single request
-
Send SMS via Twilio SDK
- Calls
client.messages.create() - Passes message body, to/from numbers, statusCallback
- Calls
-
Return Result
- On success: Returns
{number, data} - On error: Rejects with
{number, error}
- On success: Returns
Example Usage:
// Send single SMS
try {
const result = await smsProvider.sendSingleSms(
accountSID,
authToken,
'Your verification code is 123456',
'+12125550100',
'+12125551234',
'https://app.dashclicks.com/webhooks/sms-status',
);
console.log('Message SID:', result.data.sid);
console.log('Status:', result.data.status); // queued, sent
console.log('Price:', result.data.price, result.data.priceUnit); // -0.0075 USD
} catch (error) {
console.error('SMS failed:', error.number, error.error.message);
}
Twilio Message Object:
{
sid: 'SM1234567890abcdef1234567890abcdef',
accountSid: 'AC1234567890abcdef1234567890abcdef',
to: '+12125551234',
from: '+12125550100',
body: 'Your verification code is 123456',
status: 'queued', // queued → sent → delivered
numSegments: '1',
numMedia: '0',
direction: 'outbound-api',
apiVersion: '2010-04-01',
price: null, // Populated later
priceUnit: 'USD',
errorCode: null,
errorMessage: null,
uri: '/2010-04-01/Accounts/AC.../Messages/SM...',
dateCreated: 2024-01-15T10:30:00.000Z,
dateSent: null,
dateUpdated: 2024-01-15T10:30:00.000Z,
messagingServiceSid: null,
subresourceUris: {
media: '/2010-04-01/Accounts/AC.../Messages/SM.../Media'
}
}
getMessageDetail()
Retrieve details of a sent message by its SID.
Signature:
exports.getMessageDetail = async(accountSID, authToken, messageSid);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
accountSID | String | ✅ | Twilio Account SID |
authToken | String | ✅ | Twilio Auth Token |
messageSid | String | ✅ | Twilio Message SID (SM...) |
Returns:
Promise<TwilioMessageInstance>;
Business Logic:
- Create Twilio Client
- Fetch Message by SID
- Calls
client.messages(messageSid).fetch()
- Calls
- Return Full Message Details
- Includes updated status, pricing, error codes
Example Usage:
// Get message details
const message = await smsProvider.getMessageDetail(
accountSID,
authToken,
'SM1234567890abcdef1234567890abcdef',
);
console.log('Status:', message.status); // delivered, failed, undelivered
console.log('Price:', message.price, message.priceUnit); // -0.0075 USD
console.log('Segments:', message.numSegments); // 1 segment = 160 chars
if (message.errorCode) {
console.error('Error:', message.errorCode, message.errorMessage);
// Example: 21211 Invalid 'To' Phone Number
}
Common Status Values:
| Status | Description |
|---|---|
queued | Message accepted, waiting to send |
sending | Message being sent to carrier |
sent | Message sent to carrier (not delivered yet) |
delivered | ✅ Message delivered to recipient |
undelivered | ❌ Message failed to deliver (invalid number) |
failed | ❌ Message failed to send (Twilio error) |
getUSSmsPrice()
Retrieve SMS pricing information for a specific country.
Signature:
exports.getUSSmsPrice = async(accountSID, authToken, (countryCode = 'US'));
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
accountSID | String | ✅ | - | Twilio Account SID |
authToken | String | ✅ | - | Twilio Auth Token |
countryCode | String | ❌ | 'US' | ISO 3166-1 alpha-2 country code |
Returns:
Promise<{
country: string;
isoCountry: string;
priceUnit: string;
inboundSmsPrices: Array<{
type: string; // 'local', 'mobile', 'national'
basePrice: string;
currentPrice: string;
}>;
outboundSmsPrices: Array<{
mcc: string; // Mobile Country Code
mnc: string; // Mobile Network Code
carrier: string;
prices: Array<{
type: string; // 'sms'
basePrice: string;
currentPrice: string;
}>;
}>;
}>;
Business Logic:
- Create Twilio Client
- Fetch Pricing via Twilio Pricing API
- Calls
client.pricing.v2.countries(countryCode).fetch()
- Calls
- Return Pricing Structure
- Inbound SMS prices
- Outbound SMS prices by carrier
Example Usage:
// Get US SMS pricing
const pricing = await smsProvider.getUSSmsPrice(accountSID, authToken, 'US');
console.log('Country:', pricing.country); // United States
console.log('Currency:', pricing.priceUnit); // USD
// Outbound SMS price
const outbound = pricing.outboundSmsPrices[0];
console.log('Carrier:', outbound.carrier);
console.log('Price:', outbound.prices[0].currentPrice); // 0.0075
// Inbound SMS price
const inbound = pricing.inboundSmsPrices.find(p => p.type === 'local');
console.log('Inbound:', inbound.currentPrice); // 0.0075
Pricing by Country:
// Get UK pricing
const ukPricing = await smsProvider.getUSSmsPrice(accountSID, authToken, 'GB');
console.log('UK Outbound:', ukPricing.outboundSmsPrices[0].prices[0].currentPrice);
// Get Canada pricing
const caPricing = await smsProvider.getUSSmsPrice(accountSID, authToken, 'CA');
// Get Australia pricing
const auPricing = await smsProvider.getUSSmsPrice(accountSID, authToken, 'AU');
📊 SMS Status Callbacks
When a statusCallback URL is provided, Twilio sends webhook events as message status changes.
Callback Parameters (POST):
{
MessageSid: 'SM1234567890abcdef1234567890abcdef',
SmsSid: 'SM1234567890abcdef1234567890abcdef',
AccountSid: 'AC1234567890abcdef1234567890abcdef',
MessagingServiceSid: null,
From: '+12125550100',
To: '+12125551234',
Body: 'Your verification code is 123456',
MessageStatus: 'delivered', // Status change
ErrorCode: null,
ApiVersion: '2010-04-01',
DateCreated: '2024-01-15 10:30:00',
DateSent: '2024-01-15 10:30:01'
}
Webhook Handler Example:
// Express route for status callback
router.post('/webhooks/sms-status', async (req, res) => {
const { MessageSid, MessageStatus, ErrorCode, To } = req.body;
console.log(`SMS ${MessageSid} to ${To}: ${MessageStatus}`);
// Update database
await TwilioMessage.updateOne(
{ sid: MessageSid },
{ status: MessageStatus, errorCode: ErrorCode },
);
// Send notification
if (MessageStatus === 'failed' || MessageStatus === 'undelivered') {
await notifyFailedSMS(MessageSid, To, ErrorCode);
}
res.sendStatus(200);
});
💡 Integration Examples
Marketing Campaign
const smsProvider = require('./Providers/sms-api');
async function sendMarketingCampaign(campaignId) {
// Get opted-in customers
const customers = await Customer.find({
smsOptIn: true,
phone: { $exists: true },
}).lean();
const recipients = customers.map(c => c.phone);
const message = 'Limited time offer: 50% off all services! Use code SAVE50 at checkout.';
// Send bulk SMS
const result = await smsProvider.sendSms(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN,
recipients,
process.env.TWILIO_FROM_NUMBER,
message,
`https://app.dashclicks.com/campaigns/${campaignId}/sms-status`,
);
// Track results
await Campaign.updateOne(
{ _id: campaignId },
{
$set: {
sent: result.validNumberArr.length,
failed: result.invalidNumberArr.length,
status: 'sent',
},
},
);
return result;
}
Verification Code
async function sendVerificationCode(phone, code) {
try {
const result = await smsProvider.sendSingleSms(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN,
`Your DashClicks verification code is: ${code}. Valid for 10 minutes.`,
process.env.TWILIO_FROM_NUMBER,
phone,
);
// Store message SID for verification
await VerificationCode.create({
phone,
code,
messageSid: result.data.sid,
expiresAt: new Date(Date.now() + 10 * 60 * 1000),
});
return { success: true, sid: result.data.sid };
} catch (error) {
console.error('Failed to send verification:', error);
return { success: false, error: error.error.message };
}
}
SMS Delivery Monitoring
async function checkMessageDelivery(messageSid) {
const message = await smsProvider.getMessageDetail(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN,
messageSid,
);
const deliveryStatus = {
sid: message.sid,
to: message.to,
from: message.from,
status: message.status,
delivered: message.status === 'delivered',
failed: ['failed', 'undelivered'].includes(message.status),
price: message.price,
segments: message.numSegments,
errorCode: message.errorCode,
errorMessage: message.errorMessage,
};
return deliveryStatus;
}
Cost Estimation
async function estimateCampaignCost(recipientCount, countryCode = 'US') {
// Get pricing
const pricing = await smsProvider.getUSSmsPrice(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN,
countryCode,
);
// Get outbound price
const outboundPrice = parseFloat(pricing.outboundSmsPrices[0].prices[0].currentPrice);
// Calculate cost (assuming 1 segment per message)
const totalCost = Math.abs(outboundPrice * recipientCount);
return {
recipientCount,
pricePerSms: outboundPrice,
totalCost: totalCost.toFixed(2),
currency: pricing.priceUnit,
};
}
// Usage
const cost = await estimateCampaignCost(1000, 'US');
console.log(`Sending 1000 SMS will cost $${cost.totalCost}`);
// Output: Sending 1000 SMS will cost $7.50
🚨 Error Handling
Common Errors
1. Invalid Phone Number
Error: 21211 - Invalid 'To' Phone Number
- Cause: Phone number not in E.164 format or invalid
- Solution: Validate and format as
+[country][number]
2. Unverified Number (Trial Account)
Error: 21608 - The number +12125551234 is unverified
- Cause: Trial account can only send to verified numbers
- Solution: Verify number in Twilio Console or upgrade account
3. Invalid From Number
Error: 21606 - The From phone number +12125550100 is not a valid, SMS-capable inbound phone number
- Cause: From number not purchased or not SMS-capable
- Solution: Purchase SMS-capable Twilio number
4. Message Too Long
Error: 21605 - Maximum body length is 1600 characters
- Cause: Message exceeds 1600 characters
- Solution: Split message or use MMS
5. Insufficient Balance
Error: 20003 - Authentication Error
- Cause: Twilio account out of credits
- Solution: Add funds to Twilio account
Error Handling Pattern
try {
const result = await smsProvider.sendSms(accountSID, authToken, recipients, from, message);
// Handle partial failures
if (result.invalidNumberArr.length > 0) {
console.warn('Some numbers failed:', result.invalidNumberArr);
// Log failed numbers
result.sentSmsDetails.forEach(detail => {
if (detail.status === 'rejected') {
console.error(
`Failed ${detail.reason.number}:`,
detail.reason.error.code,
detail.reason.error.message,
);
}
});
}
return result;
} catch (error) {
// Handle complete failure
console.error('SMS sending failed:', error);
// Check specific error codes
if (error.code === 21211) {
throw new Error('Invalid phone number format');
} else if (error.code === 21608) {
throw new Error('Phone number not verified (trial account)');
} else if (error.code === 20003) {
throw new Error('Insufficient Twilio account balance');
}
throw error;
}
⚡ Performance Considerations
Bulk Sending
- Parallel Execution: Uses
Promise.allSettled()for concurrent sending - No Rate Limiting: Provider does not implement rate limiting
- Twilio Limits: Typically 1-10 messages per second (account-dependent)
- Recommendation: Implement queuing for large campaigns (1000+ recipients)
Message Segmentation
- Single Segment: Up to 160 characters (GSM-7 encoding)
- Multi-Segment: 153 characters per segment
- Unicode (UTF-16): 70 characters per segment
- Cost Impact: Each segment billed separately
// Calculate segments
function calculateSegments(message) {
const isUnicode = /[^\x00-\x7F]/.test(message);
const maxLength = isUnicode ? 70 : 160;
const segmentLength = isUnicode ? 67 : 153;
if (message.length <= maxLength) {
return 1;
}
return Math.ceil(message.length / segmentLength);
}
🔗 Related Documentation
- Twilio Integration Overview - Main Twilio integration
- Phone Numbers - Phone number management
- Subaccounts - Multi-tenant architecture
- Pricing - Pricing API
External Resources: