# Outbound Communications Guide
Enterprise-grade notifications without the infrastructure burden
---
## The Problem You're Not Solving Anymore
Building a production notification system typically means:
- **Weeks of provider integration** — SMTP configuration, SMS gateway contracts, telephony stack setup
- **Template management sprawl** — Hardcoded strings, no version control, developer bottlenecks for copy changes
- **Reliability engineering** — Retry logic, queue management, rate limiting, failure handling
- **Multi-channel fragmentation** — Different APIs, different patterns, different monitoring for each channel
- **Compliance overhead** — Audit trails, unsubscribe handling, delivery confirmation, GDPR considerations
The WIIL Outbound Communications system eliminates this entire layer. Your application sends a single API call. The platform handles everything else.
---
## What You Get
### Unified Multi-Channel Delivery
One SDK, one pattern, three channels. Learn it once:
```typescript
// Email
await client.outboundEmails.create({ to: [...], templateId, templateVariables });
// SMS
await client.outboundSms.create({ to, templateId, templateVariables });
// Voice Call
await client.outboundCalls.create({ to, agentConfigurationId });
```
Same structure. Same error handling. Same status tracking. Your code stays clean regardless of channel.
### Centralized Template Management
Templates live in the platform, not your codebase:
- **Marketing updates copy** — No developer involvement, no code deploy
- **Brand consistency enforced** — One source of truth across all channels
- **Variable validation** — Required fields caught at send time, not in production
- **Preview before sending** — Render templates with test data to verify output
```typescript
// Create once
await client.outboundTemplates.createEmailTemplate({
name: 'Order Shipped',
code: 'order_shipped',
subjectTemplate: 'Your order #{{orderNumber}} is on the way',
bodyHtmlTemplate: '
{{customerName}}, your order shipped!
...',
variables: [
{ key: 'customerName', required: true },
{ key: 'orderNumber', required: true },
],
});
// Use everywhere
await client.outboundEmails.create({
to: [{ email: customer.email }],
templateId: 'order_shipped',
templateVariables: { customerName: 'Jane', orderNumber: 'ORD-5521' },
});
```
### Production Infrastructure — Already Built
What the platform handles so you don't have to:
| Concern | Platform Responsibility |
| -------------- | ------------------------------------------------------ |
| **Delivery** | Provider failover, retry with exponential backoff |
| **Queuing** | High-volume batching, rate limiting, priority handling |
| **Scheduling** | Future sends, timezone-aware delivery windows |
| **Tracking** | Delivery status, bounce handling, engagement metrics |
| **Compliance** | Audit logs, unsubscribe management, data retention |
| **Monitoring** | Delivery dashboards, failure alerts, channel health |
You call `.create()`. The platform ensures it arrives.
---
## Use Cases
### Transactional Notifications
High-reliability, time-sensitive messages triggered by user actions:
```typescript
// Order confirmation — immediate
await client.outboundEmails.create({
to: [{ email: order.customerEmail }],
templateId: 'order_confirmation',
templateVariables: {
orderNumber: order.id,
total: order.total,
items: order.items.map(i => i.name).join(', '),
},
});
// Shipping update — with tracking
await client.outboundSms.create({
to: order.customerPhone,
from: businessPhone,
templateId: 'shipping_update',
templateVariables: {
trackingUrl: shipment.trackingUrl,
},
});
```
### Appointment & Booking Reminders
Reduce no-shows with automated multi-channel reminders:
```typescript
// 24-hour reminder via SMS
await client.outboundSms.create({
to: appointment.customerPhone,
from: businessPhone,
templateId: 'appointment_reminder_24h',
templateVariables: {
serviceName: appointment.serviceName,
dateTime: formatDateTime(appointment.startsAt),
location: appointment.location,
},
});
// Same-day confirmation call
await client.outboundCalls.create({
to: appointment.customerPhone,
from: businessPhone,
agentConfigurationId: 'appointment_confirmation_agent',
scheduleType: 'scheduled',
scheduledAt: appointment.startsAt - (2 * 60 * 60 * 1000), // 2 hours before
});
```
### Verification & Security
Two-factor authentication without third-party dependencies:
```typescript
const code = generateVerificationCode();
await client.outboundSms.create({
to: user.phone,
from: businessPhone,
templateId: 'verification_code',
templateVariables: { code },
});
// Template: "Your verification code is {{code}}. Valid for 10 minutes."
```
### Reservation Confirmations
Multi-channel confirmation for bookings:
```typescript
// Email with full details
await client.outboundEmails.create({
to: [{ email: guest.email, name: guest.name }],
templateId: 'reservation_confirmed',
templateVariables: {
guestName: guest.name,
checkIn: reservation.checkIn,
checkOut: reservation.checkOut,
roomType: reservation.roomType,
confirmationCode: reservation.code,
},
});
// SMS with essential info
await client.outboundSms.create({
to: guest.phone,
from: businessPhone,
templateId: 'reservation_confirmed_sms',
templateVariables: {
confirmationCode: reservation.code,
checkIn: reservation.checkIn,
},
});
```
### Proactive Customer Outreach
AI-powered outbound calls for follow-ups, surveys, and re-engagement:
```typescript
// Post-service follow-up call
await client.outboundCalls.create({
to: customer.phone,
from: businessPhone,
agentConfigurationId: 'satisfaction_survey_agent',
scheduleType: 'scheduled',
scheduledAt: serviceCompletedAt + (24 * 60 * 60), // Next day (seconds)
maxDuration: 300, // 5 minutes
maxRetries: 2,
});
```
---
## Architecture
```text
┌─────────────────────────────────────────────────────────────────────┐
│ YOUR APPLICATION │
│ │
│ client.outboundEmails.create() ← Single API call │
│ client.outboundSms.create() │
│ client.outboundCalls.create() │
└───────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ WIIL PLATFORM LAYER │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │
│ │ Template │ │ Queue │ │ Retry │ │ Status │ │
│ │ Rendering │ │ Management │ │ Logic │ │ Tracking │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │
│ │ Rate │ │ Provider │ │ Delivery │ │ Audit │ │
│ │ Limiting │ │ Failover │ │ Optimization│ │ Logs │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │
└───────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ PROVIDER INTEGRATIONS │
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Email │ │ SMS │ │ Telephony │ │
│ │ Providers │ │ Gateways │ │ Stack │ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ │
│ SendGrid, SES, Twilio, Vonage, SIP, PSTN, │
│ Mailgun, etc. MessageBird WebRTC │
└─────────────────────────────────────────────────────────────────────┘
```
**You own the top layer.** The platform owns everything else.
---
## Implementation Reference
### Setup
```typescript
import { WiilClient } from 'wiil-js';
import {
OutboundTemplateChannel,
CallRequestStatus,
EmailStatus,
SmsStatus
} from 'wiil-core-js';
const client = new WiilClient({ apiKey: process.env.WIIL_API_KEY! });
```
---
### Templates
Templates are the foundation. Create them once, use them across your entire application.
#### Email Template Creation
```typescript
const template = await client.outboundTemplates.createEmailTemplate({
name: 'Welcome Email',
code: 'welcome',
channel: OutboundTemplateChannel.EMAIL,
subjectTemplate: 'Welcome to {{companyName}}, {{firstName}}!',
bodyHtmlTemplate: `
Welcome, {{firstName}}!
Thank you for joining {{companyName}}.
`,
bodyTextTemplate: 'Welcome, {{firstName}}! Thank you for joining {{companyName}}.',
variables: [
{ key: 'firstName', required: true },
{ key: 'companyName', required: true },
],
});
```
#### SMS Template Creation
```typescript
const template = await client.outboundTemplates.createSmsTemplate({
name: 'Verification Code',
code: 'verify',
channel: 'SMS',
bodyTemplate: 'Your {{companyName}} code is {{code}}. Valid for 10 minutes.',
variables: [
{ key: 'code', required: true },
{ key: 'companyName', required: true },
],
});
```
#### Template Operations
```typescript
// Get by ID
const template = await client.outboundTemplates.get('template_123');
// Get by code (useful for deployment-independent references)
const template = await client.outboundTemplates.getByCode('welcome');
// Get all templates for a channel
const emailTemplates = await client.outboundTemplates.getByChannel(
OutboundTemplateChannel.EMAIL
);
// List all templates
const all = await client.outboundTemplates.list();
// Update email template
await client.outboundTemplates.updateEmailTemplate({
id: template.id,
subjectTemplate: 'Updated: Welcome to {{companyName}}!',
});
// Update SMS template
await client.outboundTemplates.updateSmsTemplate({
id: template.id,
bodyTemplate: 'Your code: {{code}}. Expires in 5 min.',
});
// Activate/deactivate (inactive templates cannot be used)
await client.outboundTemplates.activate(template.id);
await client.outboundTemplates.deactivate(template.id);
// Preview with test data
const preview = await client.outboundTemplates.render(template.id, {
firstName: 'Test',
companyName: 'Acme Corp',
});
console.log(preview.subject, preview.bodyHtml);
// Delete
await client.outboundTemplates.delete(template.id);
```
---
### Emails
#### Send with Template
```typescript
const result = await client.outboundEmails.create({
to: [{ email: 'customer@example.com', name: 'Jane Doe' }],
templateId: 'order_confirmation',
templateVariables: {
customerName: 'Jane',
orderNumber: 'ORD-12345',
total: '149.99',
},
});
```
#### Send with Direct Content
```typescript
const result = await client.outboundEmails.create({
to: [{ email: 'customer@example.com', name: 'Jane Doe' }],
subject: 'Your receipt',
bodyHtml: 'Thank you for your purchase!
',
bodyText: 'Thank you for your purchase!',
replyTo: 'support@example.com',
});
```
#### Multiple Recipients
```typescript
const result = await client.outboundEmails.create({
to: [
{ email: 'alice@example.com', name: 'Alice' },
{ email: 'bob@example.com', name: 'Bob' },
],
cc: [{ email: 'manager@example.com' }],
bcc: [{ email: 'archive@example.com' }],
templateId: 'team_update',
templateVariables: { ... },
});
```
#### Email Operations
```typescript
// Get by ID
const email = await client.outboundEmails.get('email_123');
// Query by status
const queued = await client.outboundEmails.getByStatus(EmailStatus.QUEUED);
const sent = await client.outboundEmails.getByStatus(EmailStatus.SENT);
// Query by template
const fromTemplate = await client.outboundEmails.getByTemplate('template_123');
// Query by date range (UTC seconds)
const nowSec = Math.floor(Date.now() / 1000);
const recent = await client.outboundEmails.getByDateRange(
nowSec - (7 * 24 * 60 * 60), // 7 days ago
nowSec
);
// List all
const all = await client.outboundEmails.list();
// Update (before sending)
await client.outboundEmails.update({ id: email.id, subject: 'Updated subject' });
// Cancel (if still queued)
await client.outboundEmails.cancel(email.id, 'Content needs revision');
// Delete
await client.outboundEmails.delete(email.id);
```
---
### SMS
#### Send with Template
```typescript
const result = await client.outboundSms.create({
to: '+14155551234',
from: '+14155555678',
templateId: 'verification_code',
templateVariables: { code: '847291' },
});
```
#### Send with Direct Content
```typescript
const result = await client.outboundSms.create({
to: '+14155551234',
from: '+14155555678',
body: 'Your appointment is confirmed for tomorrow at 2 PM.',
maxRetries: 2,
});
```
#### SMS Operations
```typescript
// Get by ID
const sms = await client.outboundSms.get('sms_123');
// Query by status
const queued = await client.outboundSms.getByStatus(SmsStatus.QUEUED);
// Query by recipient
const toNumber = await client.outboundSms.getByRecipient('+14155551234');
// Query by template
const fromTemplate = await client.outboundSms.getByTemplate('template_123');
// Query by date range
const recent = await client.outboundSms.getByDateRange(startDate, endDate);
// List all
const all = await client.outboundSms.list();
// Update (before sending)
await client.outboundSms.update({ id: sms.id, body: 'Updated message' });
// Cancel (if still queued)
await client.outboundSms.cancel(sms.id, 'Wrong recipient');
// Delete
await client.outboundSms.delete(sms.id);
```
---
### Calls
#### Immediate Call
```typescript
const result = await client.outboundCalls.create({
to: '+14155551234',
from: '+14155555678',
agentConfigurationId: 'support_agent',
scheduleType: 'immediate',
maxDuration: 300, // 5 minutes
maxRetries: 2,
});
```
#### Scheduled Call
```typescript
const result = await client.outboundCalls.create({
to: '+14155551234',
from: '+14155555678',
agentConfigurationId: 'survey_agent',
scheduleType: 'scheduled',
scheduledAt: Math.floor(Date.now() / 1000) + (2 * 60 * 60), // 2 hours from now
maxDuration: 600,
maxRetries: 3,
});
```
#### Call Operations
```typescript
// Get by ID
const call = await client.outboundCalls.get('call_123');
// Query by agent
const agentCalls = await client.outboundCalls.getByAgent('agent_config_123');
// Query by status
const pending = await client.outboundCalls.getByStatus(CallRequestStatus.PENDING);
const completed = await client.outboundCalls.getByStatus(CallRequestStatus.COMPLETED);
// Query by date range
const recent = await client.outboundCalls.getByDateRange(startDate, endDate);
// List all
const all = await client.outboundCalls.list();
// Update (reschedule)
await client.outboundCalls.update({
id: call.id,
scheduledAt: newTime,
});
// Cancel
await client.outboundCalls.cancel(call.id, 'Customer requested cancellation');
// Delete
await client.outboundCalls.delete(call.id);
```
---
### Status Enums
```typescript
import {
OutboundTemplateChannel,
CallRequestStatus,
EmailStatus,
SmsStatus
} from 'wiil-core-js';
// Template channels
OutboundTemplateChannel.EMAIL
OutboundTemplateChannel.SMS
// Call statuses
CallRequestStatus.PENDING
CallRequestStatus.SCHEDULED
CallRequestStatus.IN_PROGRESS
CallRequestStatus.COMPLETED
CallRequestStatus.FAILED
CallRequestStatus.CANCELLED
// Email statuses
EmailStatus.QUEUED
EmailStatus.SENDING
EmailStatus.SENT
EmailStatus.DELIVERED
EmailStatus.FAILED
EmailStatus.BOUNCED
EmailStatus.CANCELLED
// SMS statuses
SmsStatus.QUEUED
SmsStatus.SENDING
SmsStatus.SENT
SmsStatus.DELIVERED
SmsStatus.FAILED
SmsStatus.CANCELLED
```
---
## Complete Example: Multi-Channel Notification System
```typescript
import { WiilClient } from 'wiil-js';
import { OutboundTemplateChannel } from 'wiil-core-js';
async function initializeNotificationSystem() {
const client = new WiilClient({ apiKey: process.env.WIIL_API_KEY! });
// 1. Create your template library
const templates = {
orderConfirmation: await client.outboundTemplates.createEmailTemplate({
name: 'Order Confirmation',
code: 'order_confirmation',
channel: OutboundTemplateChannel.EMAIL,
subjectTemplate: 'Order #{{orderNumber}} Confirmed',
bodyHtmlTemplate: `
Thank you, {{customerName}}!
Your order #{{orderNumber}} is confirmed.
Total: ${{total}}
`,
bodyTextTemplate: 'Order #{{orderNumber}} confirmed. Total: ${{total}}',
variables: [
{ key: 'customerName', required: true },
{ key: 'orderNumber', required: true },
{ key: 'total', required: true },
],
}),
shippingUpdate: await client.outboundTemplates.createSmsTemplate({
name: 'Shipping Update',
code: 'shipping_update',
channel: 'SMS',
bodyTemplate: 'Your order #{{orderNumber}} shipped! Track: {{trackingUrl}}',
variables: [
{ key: 'orderNumber', required: true },
{ key: 'trackingUrl', required: true },
],
}),
verificationCode: await client.outboundTemplates.createSmsTemplate({
name: 'Verification Code',
code: 'verification',
channel: 'SMS',
bodyTemplate: 'Your code is {{code}}. Valid for 10 minutes.',
variables: [{ key: 'code', required: true }],
}),
};
// 2. Activate all templates
await Promise.all(
Object.values(templates).map(t => client.outboundTemplates.activate(t.id))
);
console.log('Notification system initialized');
return templates;
}
// Usage in your application
async function onOrderPlaced(order: Order) {
// Email confirmation
await client.outboundEmails.create({
to: [{ email: order.customer.email, name: order.customer.name }],
templateId: 'order_confirmation',
templateVariables: {
customerName: order.customer.name,
orderNumber: order.id,
total: order.total.toFixed(2),
},
});
}
async function onOrderShipped(order: Order, shipment: Shipment) {
// SMS notification
await client.outboundSms.create({
to: order.customer.phone,
from: BUSINESS_PHONE,
templateId: 'shipping_update',
templateVariables: {
orderNumber: order.id,
trackingUrl: shipment.trackingUrl,
},
});
}
async function sendVerificationCode(phone: string, code: string) {
await client.outboundSms.create({
to: phone,
from: BUSINESS_PHONE,
templateId: 'verification',
templateVariables: { code },
});
}
```
---
## Best Practices
### Use Template Codes, Not IDs
Template IDs change between environments. Codes are stable:
```typescript
// Fragile — ID differs per environment
templateId: 'tmpl_abc123'
// Robust — code is consistent
const template = await client.outboundTemplates.getByCode('order_confirmation');
await client.outboundEmails.create({ templateId: template.id, ... });
```
### Validate Templates Before Go-Live
```typescript
const preview = await client.outboundTemplates.render('order_confirmation', {
customerName: 'Test User',
orderNumber: 'TEST-001',
total: '99.99',
});
// Verify output before activating
console.log(preview.subject);
console.log(preview.bodyHtml);
```
### Handle Status Appropriately
```typescript
const result = await client.outboundEmails.create({ ... });
// The message is QUEUED, not SENT
// Use webhooks or polling for delivery confirmation
if (result.request?.status === 'QUEUED') {
console.log(`Email queued: ${result.request.id}`);
}
```
### Phone Number Format
Always use E.164 format for phone numbers:
```typescript
// Correct
to: '+14155551234'
// Incorrect
to: '(415) 555-1234'
to: '415-555-1234'
```
---
## Troubleshooting
| Issue | Cause | Solution |
| -------------------------------- | ----------------------------------- | -------------------------------------------------------- |
| "Required variable not provided" | Missing template variable | Include all required variables in `templateVariables` |
| "Template not found" | Template doesn't exist or inactive | Verify template exists and call `activate()` |
| "Invalid phone number" | Wrong format | Use E.164 format: `+[country][number]` |
| Email not delivered | Various | Check status via `getByStatus()`, verify recipient |
---
[Back to Examples](./README.md)