| Crates.io | twiml-rust |
| lib.rs | twiml-rust |
| version | 0.1.0 |
| created_at | 2026-01-20 19:16:00.679698+00 |
| updated_at | 2026-01-20 19:16:00.679698+00 |
| description | TwiML (Twilio Markup Language) library for generating Voice, Messaging, and Fax XML responses |
| homepage | |
| repository | https://github.com/Roshan0412/twiml-rust |
| max_upload_size | |
| id | 2057331 |
| size | 337,296 |
A comprehensive, type-safe Rust library for generating TwiML (Twilio Markup Language) XML responses for Voice, Messaging, and Fax applications.
TwiML is Twilio's XML-based language for controlling phone calls, SMS/MMS messages, and faxes. This library provides an idiomatic Rust API with strong type safety, builder patterns, and zero external dependencies.
Add this to your Cargo.toml:
[dependencies]
twiml-rust = "0.1.0"
Minimum Supported Rust Version (MSRV): 1.70
use twiml_rust::{VoiceResponse, TwiML};
fn main() {
let response = VoiceResponse::new()
.say("Hello! Welcome to TwiML Rust.")
.play("https://example.com/music.mp3")
.hangup();
println!("{}", response.to_xml());
}
Output:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Hello! Welcome to TwiML Rust.</Say>
<Play>https://example.com/music.mp3</Play>
<Hangup/>
</Response>
use twiml_rust::{MessagingResponse, TwiML};
fn main() {
let response = MessagingResponse::new()
.message("Thanks for your message! We'll get back to you soon.");
println!("{}", response.to_xml());
}
Output:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Message>Thanks for your message! We'll get back to you soon.</Message>
</Response>
Build sophisticated phone menus with DTMF and speech recognition:
use twiml_rust::{VoiceResponse, voice::{Gather, Say}, TwiML};
fn main() {
let gather = Gather::new()
.input(vec!["dtmf".to_string(), "speech".to_string()])
.action("https://example.com/process-input")
.timeout(10)
.num_digits(1)
.add_say(Say::new("Press 1 for sales, 2 for support, or say a department name."));
let response = VoiceResponse::new()
.say("Welcome to our automated system.")
.gather(gather)
.say("We didn't receive any input. Goodbye!")
.hangup();
println!("{}", response.to_xml());
}
Forward calls to phone numbers, SIP addresses, or Twilio clients:
use twiml_rust::{VoiceResponse, voice::{Dial, DialNumber}, TwiML};
fn main() {
let dial = Dial::new()
.timeout(30)
.caller_id("+15551234567")
.add_number(
DialNumber::new("+15559876543")
.send_digits("wwww1234") // Send DTMF after connection
);
let response = VoiceResponse::new()
.say("Please wait while we connect your call.")
.dial_with(dial)
.say("The call could not be completed. Please try again later.")
.hangup();
println!("{}", response.to_xml());
}
Record caller messages with transcription:
use twiml_rust::{VoiceResponse, voice::Record, TwiML};
fn main() {
let record = Record::new()
.action("https://example.com/handle-recording")
.method("POST")
.max_length(120)
.finish_on_key("#")
.transcribe(true)
.transcribe_callback("https://example.com/transcription");
let response = VoiceResponse::new()
.say("Please leave a message after the beep. Press pound when finished.")
.record(record)
.say("Thank you for your message. Goodbye!")
.hangup();
println!("{}", response.to_xml());
}
Send multimedia messages with images, videos, or other media:
use twiml_rust::{MessagingResponse, messaging::{Message, MessageAttributes, Body, Media}, TwiML};
fn main() {
let message = Message::with_nouns(
MessageAttributes::new()
.to("+15551234567")
.from("+15559876543")
)
.body(Body::new("Here are the photos from today's event!"))
.add_media(Media::new("https://example.com/photo1.jpg"))
.add_media(Media::new("https://example.com/photo2.jpg"))
.add_media(Media::new("https://example.com/photo3.jpg"));
let response = MessagingResponse::new()
.message_with_nouns(message);
println!("{}", response.to_xml());
}
Use SSML for fine-grained control over speech synthesis:
use twiml_rust::{VoiceResponse, voice::Say, TwiML};
fn main() {
let say = Say::new("Welcome to our service")
.voice("Polly.Joanna")
.language("en-US")
.add_break(Some("medium".to_string()), None)
.add_emphasis(Some("strong".to_string()), "Please listen carefully")
.add_break(None, Some("1s".to_string()))
.add_prosody(
Some("high".to_string()), // pitch
Some("slow".to_string()), // rate
None, // volume
"This is important information"
);
let response = VoiceResponse::new()
.say_with(say)
.hangup();
println!("{}", response.to_xml());
}
Create and manage conference calls:
use twiml_rust::{VoiceResponse, voice::{Dial, DialConference}, TwiML};
fn main() {
let conference = DialConference::new("MyConferenceRoom")
.start_conference_on_enter(true)
.end_conference_on_exit(false)
.wait_url("https://example.com/wait-music")
.max_participants(10)
.beep("true");
let dial = Dial::new().add_conference(conference);
let response = VoiceResponse::new()
.say("You are joining the conference.")
.dial_with(dial);
println!("{}", response.to_xml());
}
Configure fax reception with custom settings:
use twiml_rust::{FaxResponse, fax::{ReceiveAttributes, ReceiveMediaType, ReceivePageSize}, TwiML};
fn main() {
let response = FaxResponse::new()
.receive(Some(
ReceiveAttributes::new()
.action("https://example.com/fax-received")
.method("POST")
.media_type(ReceiveMediaType::ApplicationPdf)
.page_size(ReceivePageSize::Letter)
.store_media(true)
));
println!("{}", response.to_xml());
}
The library supports all TwiML voice verbs with comprehensive attribute support:
| Verb | Description | Key Features |
|---|---|---|
<Say> |
Text-to-speech | Multiple voices, languages, SSML support, looping |
<Play> |
Play audio files | MP3/WAV support, looping, DTMF digit playback |
<Dial> |
Make outbound calls | Numbers, SIP, clients, conferences, queues |
<Gather> |
Collect user input | DTMF, speech recognition, hints, timeouts |
<Record> |
Record audio | Transcription, max length, finish keys, dual-channel |
<Pause> |
Add silence | Configurable duration |
<Hangup> |
End the call | Immediate call termination |
<Redirect> |
Transfer control | GET/POST methods, URL redirection |
<Reject> |
Reject calls | Busy or rejected reasons |
<Enqueue> |
Queue management | Wait URLs, workflow integration, max queue size |
<Leave> |
Exit queue | Leave current queue |
<Connect> |
Advanced connections | Streams, AI sessions, conversations, virtual agents |
<Pay> |
Payment processing | Credit cards, ACH, tokenization |
<Refer> |
SIP REFER | SIP call transfer |
<Start> |
Start services | Streaming, transcription, recording, SIPREC |
<Stop> |
Stop services | Stop active streams/recordings |
<Echo> |
Echo audio | Testing and debugging |
<Sms> |
Send SMS | Send SMS during voice call |
<Prompt> |
Payment prompts | Payment card prompts |
<Queue> |
Queue caller | TaskRouter integration |
<Dial>)| Noun | Description | Use Case |
|---|---|---|
<Number> |
Phone number | Dial PSTN numbers with caller ID, machine detection |
<Client> |
Twilio Client | Dial browser/mobile clients |
<Conference> |
Conference room | Multi-party conferences with recording, coaching |
<Queue> |
Call queue | TaskRouter queue integration |
<Sip> |
SIP endpoint | Dial SIP addresses with custom headers |
<Sim> |
Twilio SIM | Dial Twilio Programmable Wireless SIMs |
<Application> |
TwiML App | Dial TwiML applications |
<WhatsApp> |
Dial WhatsApp numbers |
<Connect>)| Noun | Description | Use Case |
|---|---|---|
<Stream> |
Media streams | WebSocket audio streaming |
<Room> |
Video room | Twilio Video room connections |
<Conversation> |
Conversations | Twilio Conversations API |
<VirtualAgent> |
AI agent | Google Dialogflow integration |
<Autopilot> |
Autopilot | Twilio Autopilot assistant |
<AiSession> |
AI session | AI-powered voice agents |
<Assistant> |
Assistant | Voice assistants |
<ConversationRelay> |
Relay | Conversation relay |
<Say>)Full support for Speech Synthesis Markup Language:
| Element | Description | Example |
|---|---|---|
<break> |
Pause/silence | Strength (weak, medium, strong) or time (1s, 500ms) |
<emphasis> |
Emphasis | Levels: strong, moderate, reduced |
<prosody> |
Speech properties | Pitch, rate, volume adjustments |
<lang> |
Language switch | Switch language mid-speech |
<p> |
Paragraph | Paragraph breaks |
<s> |
Sentence | Sentence breaks |
<say-as> |
Interpret as | Numbers, dates, times, addresses, etc. |
<phoneme> |
Pronunciation | IPA, X-SAMPA phonetic alphabets |
<sub> |
Substitution | Replace text with alias |
<w> |
Word | Word-level control |
<amazon:effect> |
Voice effects | Amazon Polly effects (whispered, etc.) |
<amazon:domain> |
Speaking style | News, conversational styles |
| Verb | Description | Key Features |
|---|---|---|
<Message> |
Send SMS/MMS | Body, media, status callbacks, scheduling |
<Body> |
Message text | Plain text message content (max 1600 chars) |
<Media> |
Media attachment | Images, videos, PDFs (up to 10 per message) |
<Redirect> |
Redirect | Transfer to another TwiML URL |
| Verb | Description | Key Features |
|---|---|---|
<Receive> |
Receive fax | PDF/TIFF formats, Letter/Legal/A4 sizes, storage options |
The library includes comprehensive validation to catch errors before sending TwiML to Twilio:
use twiml_rust::{VoiceResponse, TwiML, validate_twiml};
let response = VoiceResponse::new()
.say("Hello!")
.hangup();
let xml = response.to_xml();
// Validate the generated TwiML
match validate_twiml(&xml) {
Ok(warnings) => {
if warnings.is_empty() {
println!("✓ Valid TwiML with no warnings");
} else {
println!("✓ Valid TwiML with {} warnings:", warnings.len());
for warning in warnings {
println!(" - {}", warning);
}
}
}
Err(e) => eprintln!("✗ Invalid TwiML: {}", e),
}
Enable strict validation for production environments:
use twiml_rust::validate_twiml_strict;
match validate_twiml_strict(&xml) {
Ok(warnings) => println!("Passed strict validation"),
Err(e) => eprintln!("Failed strict validation: {}", e),
}
<?xml> declaration and <Response> roothttp://, https://, or /+)The library also provides warnings for potential logic issues:
use twiml_rust::MessagingResponse;
let response = MessagingResponse::new()
.redirect("https://example.com")
.message("This will never be sent!"); // Warning: unreachable
let warnings = response.validate();
for warning in warnings {
println!("⚠ {}", warning);
}
// Output: ⚠ Warning: 1 verb(s) after Redirect at index 0 will never be reached
All user-provided content is automatically escaped to prevent XML injection attacks:
use twiml_rust::{VoiceResponse, TwiML};
let user_input = "<script>alert('xss')</script>";
let response = VoiceResponse::new().say(user_input);
println!("{}", response.to_xml());
// Output: <Say><script>alert('xss')</script></Say>
The library provides two escaping functions:
escape_xml_text() - For text content between tagsescape_xml_attr() - For attribute values (also escapes quotes)use twiml_rust::{VoiceResponse, voice::Gather, TwiML};
fn support_menu() -> String {
let gather = Gather::new()
.input(vec!["dtmf".to_string()])
.action("https://example.com/handle-menu")
.num_digits(1)
.add_say("Press 1 for account issues, 2 for technical support, 3 for billing.");
VoiceResponse::new()
.say("Thank you for calling customer support.")
.gather(gather)
.say("We didn't receive your selection. Goodbye.")
.hangup()
.to_xml()
}
use twiml_rust::{MessagingResponse, TwiML};
fn send_reminder(name: &str, date: &str, time: &str) -> String {
MessagingResponse::new()
.message(format!(
"Hi {}, this is a reminder of your appointment on {} at {}. Reply CONFIRM to confirm.",
name, date, time
))
.to_xml()
}
use twiml_rust::{VoiceResponse, voice::Record, TwiML};
fn voicemail_system() -> String {
let record = Record::new()
.action("https://example.com/save-recording")
.max_length(180)
.transcribe(true)
.transcribe_callback("https://example.com/transcription")
.play_beep(true);
VoiceResponse::new()
.say("You have reached the voicemail of John Doe.")
.say("Please leave a message after the beep.")
.record(record)
.say("Thank you. Your message has been recorded.")
.hangup()
.to_xml()
}
use twiml_rust::{MessagingResponse, TwiML};
fn send_2fa_code(code: &str) -> String {
MessagingResponse::new()
.message(format!("Your verification code is: {}. Do not share this code.", code))
.to_xml()
}
See the examples/ directory for complete, runnable examples:
voice_call.rs - Voice call handling with IVR, call forwarding, voicemail, and SSMLsms_message.rs - SMS/MMS messaging with media attachments and attributesfax_receive.rs - Fax reception configuration with various optionsRun examples with:
cargo run --example voice_call
cargo run --example sms_message
cargo run --example fax_receive
The library is built around the TwiML trait:
pub trait TwiML {
fn to_xml(&self) -> String;
}
All response types (VoiceResponse, MessagingResponse, FaxResponse) implement this trait, providing a consistent interface for XML generation.
All verbs and nouns use the builder pattern for ergonomic API:
let dial = Dial::new()
.timeout(30)
.caller_id("+15551234567")
.record("record-from-answer")
.add_number(DialNumber::new("+15559876543"));
The library uses Rust's type system to prevent invalid TwiML:
ReceiveMediaType::ApplicationPdf)DialNoun, GatherNoun)Add XML comments for debugging or documentation:
use twiml_rust::{VoiceResponse, TwiML};
let response = VoiceResponse::new()
.comment_before("Generated by my application v1.0")
.comment("Main greeting")
.say("Hello!")
.comment_after("End of TwiML");
println!("{}", response.to_xml());
Output:
<!-- Generated by my application v1.0 -->
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<!-- Main greeting -->
<Say>Hello!</Say>
</Response>
<!-- End of TwiML -->
Fine-tune text-to-speech with voices and languages:
use twiml_rust::{VoiceResponse, voice::Say, TwiML};
let say = Say::new("Hello, how are you?")
.voice("Polly.Joanna") // Amazon Polly voice
.language("en-US") // US English
.loop_count(2); // Repeat twice
let response = VoiceResponse::new().say_with(say);
Supported voices include:
Polly.Joanna, Polly.Matthew, Polly.Salli, etc.Google.en-US-Standard-A, Google.en-GB-Wavenet-B, etc.man, woman, aliceDetect answering machines when dialing:
use twiml_rust::{VoiceResponse, voice::{Dial, DialNumber}, TwiML};
let dial = Dial::new()
.add_number(
DialNumber::new("+15551234567")
.machine_detection("DetectMessageEnd")
.machine_detection_timeout(30)
.amd_status_callback("https://example.com/amd-status")
);
let response = VoiceResponse::new().dial_with(dial);
Dial SIP endpoints with custom headers:
use twiml_rust::{VoiceResponse, voice::{Dial, DialSip}, TwiML};
let sip = DialSip::new("sip:user@example.com")
.username("myuser")
.password("mypass")
.add_custom_header("X-Custom-Header", "value");
let dial = Dial::new().add_sip(sip);
let response = VoiceResponse::new().dial_with(dial);
Stream audio to WebSocket endpoints:
use twiml_rust::{VoiceResponse, voice::{Connect, Stream}, TwiML};
let stream = Stream::new()
.url("wss://example.com/stream")
.track("both_tracks")
.status_callback("https://example.com/stream-status");
let connect = Connect::new().add_stream(stream);
let response = VoiceResponse::new().connect(connect);
Collect payment information securely:
use twiml_rust::{VoiceResponse, voice::Pay, TwiML};
let pay = Pay::new()
.charge_amount("19.99")
.currency("USD")
.payment_method("credit-card")
.action("https://example.com/payment-complete")
.status_callback("https://example.com/payment-status");
let response = VoiceResponse::new()
.say("Please enter your payment information.")
.pay(pay);
The library includes 167+ comprehensive tests covering:
Run tests with:
cargo test
Run tests with output:
cargo test -- --nocapture
Run specific test module:
cargo test voice_tests
cargo test messaging_tests
cargo test validation_tests
Benchmark (simple voice response):
Time to generate: ~500ns
Memory allocated: ~200 bytes
Contributions are welcome! Here's how you can help:
# Clone the repository
git clone https://github.com/Roshan0412/twiml-rust.git
cd twiml-rust
# Run tests
cargo test
# Run examples
cargo run --example voice_call
# Check formatting
cargo fmt --check
# Run clippy
cargo clippy -- -D warnings
# Build documentation
cargo doc --open
This project is licensed under the MIT License - see the LICENSE file for details.
Copyright (c) 2026 TwiML Rust Contributors
See CHANGELOG.md for a detailed history of changes.
A: No, this library only generates TwiML XML. You need to serve the XML from your web server in response to Twilio's HTTP requests.
A: Yes! The library is sync but works perfectly in async contexts. Just call to_xml() to generate the XML string.
A: Use any Rust web framework (Actix, Axum, Rocket, etc.) and return the XML with Content-Type: application/xml:
// Example with Axum
use axum::{response::IntoResponse, http::header};
use twiml_rust::{VoiceResponse, TwiML};
async fn handle_call() -> impl IntoResponse {
let twiml = VoiceResponse::new()
.say("Hello from Rust!")
.to_xml();
(
[(header::CONTENT_TYPE, "application/xml")],
twiml
)
}
A: Yes! The library is thoroughly tested with 167+ tests and follows Rust best practices. However, always validate your TwiML before deploying to production.
say() and say_with()?A: say() is a convenience method for simple text. say_with() accepts a Say object with custom attributes and SSML elements.
A: Absolutely! Please open an issue first to discuss, then submit a PR with tests and documentation.
Made with Rust | Open Source & Community Driven | TwiML XML Generator