Documentation Index Fetch the complete documentation index at: https://docs.ensemble.ai/llms.txt
Use this file to discover all available pages before exploring further.
Basic Usage
operations :
- name : send-otp
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : ${input.phone}
body : Your verification code is : ${input.code}
Configuration
config :
provider : string # twilio, vonage, aws-sns
from : string # Sender phone number (E.164 format)
to : string | string[] # Recipient(s) (E.164 format)
body : string # Message text (max 160 chars for 1 segment)
mediaUrl : string[] # MMS media URLs (Twilio only, optional)
accountSid : string # Twilio Account SID
authToken : string # Twilio Auth Token
apiKey : string # Vonage API Key
apiSecret : string # Vonage API Secret
accessKeyId : string # AWS Access Key ID
secretAccessKey : string # AWS Secret Access Key
region : string # AWS Region
SMS Providers
Twilio (Recommended)
Industry-leading SMS provider with 99.95% uptime and global coverage:
operations :
- name : send
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : +1234567890
body : Your verification code is 123456
Setup :
Sign up at twilio.com
Get Account SID and Auth Token from console
Purchase a phone number or create Messaging Service
Set secrets:
wrangler secret put TWILIO_ACCOUNT_SID
wrangler secret put TWILIO_AUTH_TOKEN
wrangler secret put TWILIO_PHONE_NUMBER
Pricing :
US/Canada: $0.0079/SMS
UK: $0.04/SMS
India: $0.0057/SMS
Full pricing
Rate Limits :
Standard: 100 SMS/second
Messaging Service: 1,000 SMS/second
Free trial: 1 SMS/second
Cost-effective SMS with strong international coverage:
operations :
- name : send
operation : sms
config :
provider : vonage
apiKey : ${env.VONAGE_API_KEY}
apiSecret : ${env.VONAGE_API_SECRET}
from : "Acme" # Alphanumeric sender ID
to : +442071234567
body : Your code is 123456
Setup :
Sign up at vonage.com
Get API Key and Secret
Set secrets:
wrangler secret put VONAGE_API_KEY
wrangler secret put VONAGE_API_SECRET
Pricing :
US: $0.0057/SMS
UK: $0.0331/SMS
Lower than Twilio in many regions
AWS SNS
Integrate with AWS infrastructure:
operations :
- name : send
operation : sms
config :
provider : aws-sns
accessKeyId : ${env.AWS_ACCESS_KEY_ID}
secretAccessKey : ${env.AWS_SECRET_ACCESS_KEY}
region : us-east-1
from : +1234567890
to : +1234567891
body : Alert : Server CPU at 95%
Setup :
Enable SNS in AWS Console
Create IAM user with SNS permissions
Set secrets:
wrangler secret put AWS_ACCESS_KEY_ID
wrangler secret put AWS_SECRET_ACCESS_KEY
All phone numbers MUST use E.164 format:
Valid Examples :
US: +1234567890
UK: +442071234567
France: +33123456789
Japan: +81312345678
India: +919876543210
Invalid Examples (will be rejected):
1234567890 - Missing + prefix
+1 (234) 567-8900 - Contains formatting
+1-234-567-8900 - Contains dashes
001-234-567-8900 - Wrong prefix
Common Use Cases
OTP Verification
operations :
# Generate OTP
- name : generate-otp
operation : code
config :
script : scripts/generate-otp-code
# Send OTP
- name : send-otp
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : ${input.phone}
body : Your verification code is ${generate-otp.output.code}. Valid for 5 minutes.
# Store for verification
- name : store-otp
operation : storage
config :
type : kv
action : put
key : otp:${input.phone}
value : ${generate-otp.output.code}
ttl : 300 # 5 minutes
// scripts/generate-otp-code.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function generateOtpCode ( context : AgentExecutionContext ) {
const code = Math . floor ( 100000 + Math . random () * 900000 ). toString ()
return {
code ,
expiresAt: Date . now () + 300000 // 5 minutes
}
}
### Two-Factor Authentication
``` yaml
ensemble : send-2fa-code
inputs :
userId : string
operations :
# Get user phone
- name : get-user
operation : storage
config :
type : d1
query : SELECT phone FROM users WHERE id = ?
params : [ $ { input.userId }]
# Generate 2FA code
- name : generate-code
operation : code
config :
script : scripts/generate-2fa-code
# Send 2FA SMS
- name : send-2fa
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : ${get-user.output.results[0].phone}
body : |
Your MyApp verification code is: ${generate-code.output.code}
This code expires in 10 minutes.
Never share this code with anyone.
# Log attempt
- name : log-attempt
operation : storage
config :
type : d1
query : |
INSERT INTO auth_attempts (user_id, code, created_at)
VALUES (?, ?, datetime('now'))
params :
- ${input.userId}
- ${generate-code.output.code}
// scripts/generate-2fa-code.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function generate2faCode ( context : AgentExecutionContext ) {
return {
code: Math . floor ( 100000 + Math . random () * 900000 ). toString ()
}
}
outputs :
success : true
code : ${generate-code.output.code}
System Alerts
operations :
- name : alert-admin
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : ${env.ADMIN_PHONE}
body : |
[ALERT] ${input.alert_type}
${input.message}
Time: ${new Date().toISOString()}
Order Notifications
operations :
- name : notify-customer
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : ${input.customer_phone}
body : |
Hi ${input.customer_name}! Your order #${input.order_id} is ready for pickup.
Total: $${input.total}
Questions? Reply to this message.
Appointment Reminders
operations :
- name : send-reminder
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : ${input.patient_phone}
body : |
Reminder: You have an appointment tomorrow at ${input.appointment_time}.
Reply C to confirm or R to reschedule.
Batch Sending
Send personalized SMS to multiple recipients:
operations :
# Fetch customers
- name : get-customers
operation : storage
config :
type : d1
query : |
SELECT phone, name, balance
FROM customers
WHERE opt_in_sms = true
LIMIT 100
# Send batch SMS
- name : send-batch
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
batch :
recipients : ${get-customers.output.results}
phoneField : phone # Field containing phone number
template : |
Hi {{name}}, your account balance is ${{balance}}.
Reply STOP to unsubscribe.
rateLimit : 5 # 5 SMS per second
Output :
{
sent : 98 ,
failed : 2 ,
messageIds : [ "SM123..." , "SM124..." , ... ],
errors : [
{ phone : "+1234567890" , error : "Invalid phone number" },
{ phone : "+1234567891" , error : "Blocked number" }
]
}
Send images and media with Twilio:
operations :
- name : send-mms
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : ${input.phone}
body : Check out this product!
mediaUrl :
- https://example.com/product-image.jpg
- https://example.com/product-specs.pdf
Supported Media :
Images: JPG, PNG, GIF
Video: MP4, 3GP
Audio: MP3, WAV
Documents: PDF
Max size: 5 MB per file
Max files: 10 per message
MMS Pricing :
US/Canada: 0.02 / M M S ( v s 0.02/MMS (vs 0.02/ MMS ( v s 0.0079/SMS)
International: Varies by country
Template Rendering
Use Liquid templates for dynamic content:
operations :
- name : send-welcome
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : ${input.phone}
template : liquid
body : |
Welcome {{ name | capitalize }}!
Account balance: {{ balance | money: "USD" }}
{% if premium %}You have premium access.{% endif %}
Rate Limiting
Control sending rate to respect provider limits:
operations :
- name : send-batch
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
batch :
recipients : ${input.recipients}
rateLimit : 3 # 3 SMS per second
Recommended Rates :
Twilio Free Trial: 1 SMS/sec
Twilio Standard: 10 SMS/sec
Twilio Messaging Service: 100 SMS/sec
Vonage: 10 SMS/sec
AWS SNS: 20 SMS/sec
Error Handling
Retry on Failure
operations :
- name : send
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : ${input.phone}
body : ${input.message}
retry :
maxAttempts : 3
backoff : exponential # 1s, 2s, 4s delays
Fallback Provider
operations :
# Try Twilio first
- name : send-twilio
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : ${input.phone}
body : ${input.message}
# Fallback to Vonage
- name : send-vonage
condition : ${send-twilio.failed}
operation : sms
config :
provider : vonage
apiKey : ${env.VONAGE_API_KEY}
apiSecret : ${env.VONAGE_API_SECRET}
from : "Acme"
to : ${input.phone}
body : ${input.message}
outputs :
sent : ${send-twilio.success || send-vonage.success}
provider : ${send-twilio.success ? 'twilio' : 'vonage' }
Validate Phone Numbers
operations :
# Validate E.164 format
- name : validate
operation : code
config :
script : scripts/validate-phone-e164-sms
input :
phone : ${input.phone}
# Send if valid
- name : send
condition : ${validate.output.valid}
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
to : ${input.phone}
body : ${input.message}
// scripts/validate-phone-e164-sms.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function validatePhoneE164Sms ( context : AgentExecutionContext ) {
const { phone } = context . input
const e164Regex = / ^ \+ [ 1-9 ] \d {1,14} $ /
if ( ! e164Regex . test ( phone )) {
throw new Error ( 'Invalid phone number format. Use E.164: +1234567890' )
}
return { valid: true }
}
### Handle Batch Errors
``` yaml
operations :
- name : send-batch
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
from : ${env.TWILIO_PHONE_NUMBER}
batch :
recipients : ${input.recipients}
# Log failed sends
- name : log-errors
condition : ${send-batch.output.failed > 0}
operation : storage
config :
type : kv
action : put
key : sms-errors:${Date.now()}
value : ${send-batch.output.errors}
outputs :
sent : ${send-batch.output.sent}
failed : ${send-batch.output.failed}
successRate : ${(send-batch.output.sent / (send-batch.output.sent + send-batch.output.failed)) * 100}
Testing
Test with TestConductor
import { describe , it , expect } from 'vitest' ;
import { TestConductor } from '@ensemble/conductor/testing' ;
describe ( 'send-otp' , () => {
it ( 'should send OTP via SMS' , async () => {
const conductor = await TestConductor . create ({
projectPath: './conductor' ,
mocks: {
sms: {
'send-otp' : {
success: true ,
messageId: 'SM123456' ,
status: 'sent' ,
provider: 'twilio'
}
}
}
});
const result = await conductor . executeAgent ( 'send-otp' , {
phone: '+1234567890' ,
code: '123456'
});
expect ( result ). toBeSuccessful ();
expect ( result . output . messageId ). toBe ( 'SM123456' );
});
it ( 'should handle invalid phone number' , async () => {
const conductor = await TestConductor . create ({
projectPath: './conductor'
});
const result = await conductor . executeAgent ( 'send-otp' , {
phone: 'invalid' ,
code: '123456'
});
expect ( result ). toBeFailed ();
expect ( result . error ). toContain ( 'Invalid phone number' );
});
});
Mock SMS Provider
const conductor = await TestConductor . create ({
mocks: {
sms: {
'*' : { // Mock all SMS operations
success: true ,
messageId: 'mock-sms-id' ,
status: 'sent' ,
timestamp: new Date (). toISOString ()
}
}
}
});
Best Practices
1. Keep Messages Short
# Good: 160 characters or less
body : Your code is 123456. Valid for 5 minutes.
# Bad: Too long (3 segments = 3x cost)
body : |
Hello! Your verification code for accessing your account on
our platform is 123456. This code will expire in 5 minutes.
If you did not request this code, please ignore this message.
2. Include Opt-Out Instructions
# Good: Include unsubscribe
body : |
Hi ${name}! Order #${orderId} is ready.
Reply STOP to unsubscribe.
# Bad: No opt-out
body : Hi ${name}! Order #${orderId} is ready.
3. Validate Before Sending
# Good: Validate E.164 format
operations :
- name : validate
operation : code
config :
script : scripts/validate-phone-simple
input :
phone : ${input.phone}
- name : send
operation : sms
# Bad: No validation
operations :
- name : send
operation : sms
config :
to : ${input.phone} # Could be invalid
// scripts/validate-phone-simple.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function validatePhoneSimple ( context : AgentExecutionContext ) {
const { phone } = context . input
if ( ! / ^ \+ [ 1-9 ] \d {1,14} $ / . test ( phone )) {
throw new Error ( 'Invalid phone' )
}
return { valid: true }
}
* *4. Rate Limit Batch Sends**
``` yaml
# Good: Set rate limit
operations :
- name : send-batch
operation : sms
config :
batch :
recipients : ${input.recipients}
rateLimit : 5
# Bad: No rate limiting
operations :
- name : send-batch
operation : sms
config :
batch :
recipients : ${input.recipients}
5. Store Credentials Securely
# Good: Use secrets
operations :
- name : send
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
# Bad: Hardcoded credentials
operations :
- name : send
operation : sms
config :
provider : twilio
accountSid : AC1234567890 # Never do this!
authToken : secret123
6. Use Messaging Services for Scale
# Good: Use Messaging Service for high volume
operations :
- name : send-campaign
operation : sms
config :
provider : twilio
accountSid : ${env.TWILIO_ACCOUNT_SID}
authToken : ${env.TWILIO_AUTH_TOKEN}
messagingServiceSid : ${env.TWILIO_MESSAGING_SERVICE_SID}
# Bad: Single phone number (limited throughput)
operations :
- name : send-campaign
operation : sms
config :
provider : twilio
from : ${env.TWILIO_PHONE_NUMBER}
7. Monitor Delivery Status
# Good: Log and monitor
operations :
- name : send
operation : sms
- name : log-status
operation : storage
config :
type : d1
query : |
INSERT INTO sms_log (phone, status, message_id, timestamp)
VALUES (?, ?, ?, datetime('now'))
params :
- ${input.phone}
- ${send.output.status}
- ${send.output.messageId}
# Bad: No logging or monitoring
operations :
- name : send
operation : sms
8. Respect Time Zones
# Good: Check time before sending
operations :
- name : check-time
operation : code
config :
script : scripts/check-business-hours
- name : send
condition : ${check-time.output.canSend}
operation : sms
# Bad: Send anytime
operations :
- name : send
operation : sms
// scripts/check-business-hours.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function checkBusinessHours ( context : AgentExecutionContext ) {
const hour = new Date (). getHours ()
// Don't send between 9 PM and 8 AM
if ( hour >= 21 || hour < 8 ) {
throw new Error ( 'Outside business hours' )
}
return { canSend: true }
}
## Common Pitfalls
### Pitfall: Wrong Phone Format
``` yaml
# Bad: Missing + or wrong format
- name : send
operation : sms
config :
to : 1234567890
# Good: E.164 format
- name : send
operation : sms
config :
to : +1234567890
Pitfall: Too Long Messages
# Bad: 300 characters (2 segments = 2x cost)
body : |
Your verification code is 123456. This code will expire
in 5 minutes. If you did not request this code, please
ignore this message. For support, contact us at...
# Good: 60 characters (1 segment)
body : Your code is 123456. Valid for 5 minutes.
Pitfall: No Error Handling
# Bad: No retry or fallback
- name : send
operation : sms
config :
provider : twilio
# Good: Retry and fallback
- name : send-twilio
operation : sms
config :
provider : twilio
retry :
maxAttempts : 3
- name : send-vonage
condition : ${send-twilio.failed}
operation : sms
config :
provider : vonage
Pitfall: Missing Opt-Out
# Bad: No unsubscribe info (violates regulations)
body : Special offer! Buy now!
# Good: Include opt-out
body : Special offer! Buy now! Reply STOP to unsubscribe.
SMS vs Email Comparison
Feature SMS Email Open Rate 98% 20% Delivery Speed Instant Minutes to hours Cost 0.0075 − 0.0075- 0.0075 − 0.04/msg0.0001 − 0.0001- 0.0001 − 0.001/msgLength 160 chars (1 segment) Unlimited Media MMS only (5 MB) Full HTML, attachments Best For Alerts, OTP, urgent Newsletters, receipts, long-form Compliance TCPA, GDPR CAN-SPAM, GDPR
Troubleshooting
SMS Not Delivering
Verify phone format - Must be E.164 (+1234567890)
Check provider credentials - Ensure Account SID/Auth Token correct
Review rate limits - You may be hitting throttles
Check provider console - View delivery reports
Test with own number - Confirm basic functionality
Rate Limit Errors
# Reduce sending rate
config :
rateLimit : 1 # 1 SMS per second
Invalid Phone Number Errors
// Validate E.164 format
function validateE164 ( phone : string ) : boolean {
return / ^ \+ [ 1-9 ] \d {1,14} $ / . test ( phone );
}
// Remove formatting
function formatToE164 ( phone : string , countryCode : string = '+1' ) : string {
const digits = phone . replace ( / \D / g , '' );
return ` ${ countryCode }${ digits } ` ;
}
Message Too Long
Single SMS : 160 chars (GSM-7) or 70 chars (Unicode)
Concatenated SMS : Up to 1600 chars (10 segments)
Cost : Each segment charged separately
Solution : Shorten message or use link shorteners
Next Steps
email Operation Compare with email
storage Operation Store OTP codes
code Operation Generate verification codes
Testing Test SMS workflows