Skip to main content

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:

ParameterTypeRequiredDescription
accountSIDStringTwilio Account SID or Subaccount SID
authTokenStringTwilio Auth Token
recipientsArrayArray of phone numbers (E.164 format)
fromStringSender phone number (must be Twilio-owned)
contentsStringSMS message content
statusCallbackStringWebhook 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:

  1. Create Twilio Client

    • Creates client with provided accountSID and authToken
    • Client is instantiated per request (no connection pooling)
  2. Send SMS to Each Recipient

    • Iterates through recipients array
    • Calls sendSingleSms() for each number
    • Uses Promise.allSettled() to handle parallel sending
  3. Categorize Results

    • Successful sends → sentSmsDetails array
    • Fulfilled promises → validNumberArr
    • Rejected promises → invalidNumberArr

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:

ParameterTypeRequiredDescription
accountSIDStringTwilio Account SID
authTokenStringTwilio Auth Token
contentsStringSMS message content
fromStringSender phone number
numberStringRecipient phone number (E.164 format)
statusCallbackStringWebhook URL for status updates

Returns:

Promise<{
number: string;
data: TwilioMessageInstance; // Twilio message object
}>;

Business Logic:

  1. Create Twilio Client

    • Instantiates client with account credentials
    • Client is scoped to this single request
  2. Send SMS via Twilio SDK

    • Calls client.messages.create()
    • Passes message body, to/from numbers, statusCallback
  3. Return Result

    • On success: Returns {number, data}
    • On error: Rejects with {number, error}

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:

ParameterTypeRequiredDescription
accountSIDStringTwilio Account SID
authTokenStringTwilio Auth Token
messageSidStringTwilio Message SID (SM...)

Returns:

Promise<TwilioMessageInstance>;

Business Logic:

  1. Create Twilio Client
  2. Fetch Message by SID
    • Calls client.messages(messageSid).fetch()
  3. 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:

StatusDescription
queuedMessage accepted, waiting to send
sendingMessage being sent to carrier
sentMessage 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:

ParameterTypeRequiredDefaultDescription
accountSIDString-Twilio Account SID
authTokenString-Twilio Auth Token
countryCodeString'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:

  1. Create Twilio Client
  2. Fetch Pricing via Twilio Pricing API
    • Calls client.pricing.v2.countries(countryCode).fetch()
  3. 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);
}

External Resources:

💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM