Skip to main content

Outbound Communications

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.

SDK parity matrix

This reflects what the SDK example guides demonstrate for each language.

AreaTypeScriptPython
Unified send (email / SMS / call)
Templates — create + render
Templates — get, list, update, activate, deactivate, delete
Email — send with template
Email — direct content, multiple recipients, operations
SMS — send with template
SMS — direct content, operations
Calls — immediate, scheduled
Calls — operations
Status — query by status
Use cases — transactional, reminders, scheduled calls
Use cases — verification, reservation confirmations

What you get

Unified multi-channel delivery

One SDK, one pattern, three channels.

// Email
await client.outboundEmails.create({ to: [...], templateId, templateVariables });

// SMS
await client.outboundSms.create({ to, templateId, templateVariables });

// Voice Call
await client.outboundCalls.create({ to, agentConfigurationId });

Centralized template management

Templates live in the platform, not your codebase. Create once; reuse everywhere.

// Create once
await client.outboundTemplates.createEmailTemplate({
name: 'Order Shipped',
code: 'order_shipped',
subjectTemplate: 'Your order #{{orderNumber}} is on the way',
bodyHtmlTemplate: '<h1>{{customerName}}, your order shipped!</h1>...',
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

ConcernPlatform responsibility
DeliveryProvider failover, retry with exponential backoff
QueuingHigh-volume batching, rate limiting, priority handling
SchedulingFuture sends, timezone-aware delivery windows
TrackingDelivery status, bounce handling, engagement metrics
ComplianceAudit logs, unsubscribe management, data retention
MonitoringDelivery dashboards, failure alerts, channel health

You call .create(). The platform ensures it arrives.

Use cases

Transactional notifications

// 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 reminders

// 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
});

Scheduled / proactive calls

AI-powered outbound calls for follow-ups, surveys, and re-engagement.

// 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,
});

Verification & security (TypeScript)

Two-factor authentication without third-party dependencies:

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 (TypeScript)

Multi-channel confirmation for bookings:

// 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,
},
});

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│ 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

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 — creation

const template = await client.outboundTemplates.createEmailTemplate({
name: 'Welcome Email',
code: 'welcome',
channel: OutboundTemplateChannel.EMAIL,
subjectTemplate: 'Welcome to {{companyName}}, {{firstName}}!',
bodyHtmlTemplate: `
<h1>Welcome, {{firstName}}!</h1>
<p>Thank you for joining {{companyName}}.</p>
`,
bodyTextTemplate: 'Welcome, {{firstName}}! Thank you for joining {{companyName}}.',
variables: [
{ key: 'firstName', required: true },
{ key: 'companyName', required: true },
],
});

const smsTemplate = 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 },
],
});

Templates — 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',
});

// Delete
await client.outboundTemplates.delete(template.id);

Emails — send with template

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',
},
});

Emails — direct content & operations (TypeScript)

// Direct content
const result = await client.outboundEmails.create({
to: [{ email: 'customer@example.com', name: 'Jane Doe' }],
subject: 'Your receipt',
bodyHtml: '<h1>Thank you for your purchase!</h1>',
bodyText: 'Thank you for your purchase!',
replyTo: 'support@example.com',
});

// Multiple recipients
const result2 = 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: { ... },
});

// Operations
const email = await client.outboundEmails.get('email_123');
const queued = await client.outboundEmails.getByStatus(EmailStatus.QUEUED);
const sent = await client.outboundEmails.getByStatus(EmailStatus.SENT);
const fromTemplate = await client.outboundEmails.getByTemplate('template_123');
const nowSec = Math.floor(Date.now() / 1000);
const recent = await client.outboundEmails.getByDateRange(nowSec - (7 * 24 * 60 * 60), nowSec);
const all = await client.outboundEmails.list();
await client.outboundEmails.update({ id: email.id, subject: 'Updated subject' });
await client.outboundEmails.cancel(email.id, 'Content needs revision');
await client.outboundEmails.delete(email.id);

SMS — send with template

const result = await client.outboundSms.create({
to: '+14155551234',
from: '+14155555678',
templateId: 'verification_code',
templateVariables: { code: '847291' },
});

SMS — direct content & operations (TypeScript)

// Direct content
const result = await client.outboundSms.create({
to: '+14155551234',
from: '+14155555678',
body: 'Your appointment is confirmed for tomorrow at 2 PM.',
maxRetries: 2,
});

// Operations
const sms = await client.outboundSms.get('sms_123');
const queued = await client.outboundSms.getByStatus(SmsStatus.QUEUED);
const toNumber = await client.outboundSms.getByRecipient('+14155551234');
const fromTemplate = await client.outboundSms.getByTemplate('template_123');
const recent = await client.outboundSms.getByDateRange(startDate, endDate);
const all = await client.outboundSms.list();
await client.outboundSms.update({ id: sms.id, body: 'Updated message' });
await client.outboundSms.cancel(sms.id, 'Wrong recipient');
await client.outboundSms.delete(sms.id);

Calls — immediate

const result = await client.outboundCalls.create({
to: '+14155551234',
from: '+14155555678',
agentConfigurationId: 'support_agent',
scheduleType: 'immediate',
maxDuration: 300, // 5 minutes
maxRetries: 2,
});

Calls — scheduled

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,
});

Calls — operations (TypeScript)

const call = await client.outboundCalls.get('call_123');
const agentCalls = await client.outboundCalls.getByAgent('agent_config_123');
const pending = await client.outboundCalls.getByStatus(CallRequestStatus.PENDING);
const completed = await client.outboundCalls.getByStatus(CallRequestStatus.COMPLETED);
const recent = await client.outboundCalls.getByDateRange(startDate, endDate);
const all = await client.outboundCalls.list();
await client.outboundCalls.update({ id: call.id, scheduledAt: newTime });
await client.outboundCalls.cancel(call.id, 'Customer requested cancellation');
await client.outboundCalls.delete(call.id);

Status tracking

import { CallRequestStatus, EmailStatus, SmsStatus } from 'wiil-core-js';

const pendingCalls = await client.outboundCalls.getByStatus(CallRequestStatus.PENDING);
const queuedEmails = await client.outboundEmails.getByStatus(EmailStatus.QUEUED);
const queuedSms = await client.outboundSms.getByStatus(SmsStatus.QUEUED);

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: `
<h1>Thank you, {{customerName}}!</h1>
<p>Your order #{{orderNumber}} is confirmed.</p>
<p><strong>Total:</strong> ${{total}}</p>
`,
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:

// 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

const preview = await client.outboundTemplates.render('order_confirmation', {
customerName: 'Test User',
orderNumber: 'TEST-001',
total: '99.99',
});

Handle status appropriately

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: +[country][number] (for example +14155551234).

Troubleshooting

IssueCauseSolution
"Required variable not provided"Missing template variableInclude all required variables in templateVariables / variables
"Template not found"Template doesn't exist or inactiveVerify template exists and call activate()
"Invalid phone number"Wrong formatUse E.164 format: +[country][number]
Email not deliveredVariousCheck status via getByStatus() / get_by_status(), verify recipient

Production checklist (Python guide)

  • Store all phone numbers in E.164 format.
  • Keep message templates in the platform when copy changes frequently.
  • Persist outbound request IDs so you can reconcile delivery status.
  • Configure retry counts deliberately per channel.
  • Respect local consent, opt-out, and calling-hour rules.
  • Use metadata to attach your own order, appointment, or campaign IDs.