> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Templates API

> Send template messages programmatically

## Overview

The Templates API allows you to list, retrieve, and send template messages programmatically. Templates are pre-approved message formats for proactive communication across different channels.

<Note>
  **Sending a template as part of a conversation?** [`Channels.whatsapp.sendTemplate`](/api/channels#channels-whatsapp-sendtemplate) is the canonical way to send a WhatsApp template to a specific user — it resolves the recipient by `userId` or phone number and records the send to the conversation thread, so your agent remembers the outreach. Use the `Templates.whatsapp.send` below for **bulk / campaign** sends by channel ID and a list of phone numbers. Both are supported; pick by use case.
</Note>

## Supported Template Types

<CardGroup cols={1}>
  <Card title="WhatsApp Templates" icon="whatsapp" iconType="brands">
    Pre-approved message formats for WhatsApp Business Accounts. Required for initiating conversations outside the 24-hour messaging window.
  </Card>
</CardGroup>

<Note>
  Additional template types may be added in the future. The API is designed to work consistently across different template types.
</Note>

## Quick Start

```typescript theme={null}
import { Templates } from 'lua-cli';

// List all WhatsApp templates for a channel
const result = await Templates.whatsapp.list(channelId);
console.log(result.templates);  // Array of templates
console.log(result.total);      // Total count

// Get a specific template
const template = await Templates.whatsapp.get(channelId, 'template_123');
console.log(template.name);   // Template name
console.log(template.status); // 'APPROVED', 'PENDING', etc.

// Send a WhatsApp template message
const response = await Templates.whatsapp.send(channelId, 'template_123', {
  phoneNumbers: ['+447551166594'],
  values: {
    body: { first_name: 'John', order_number: '12345' }
  }
});
```

<CardGroup cols={2}>
  <Card title="Template Listing" icon="list">
    List and search templates with pagination
  </Card>

  <Card title="Template Retrieval" icon="magnifying-glass">
    Get template details including components
  </Card>

  <Card title="Batch Sending" icon="paper-plane">
    Send to multiple recipients at once
  </Card>

  <Card title="Dynamic Values" icon="wand-magic-sparkles">
    Fill template parameters dynamically
  </Card>
</CardGroup>

## WhatsApp Templates

Use `Templates.whatsapp` for WhatsApp Business templates.

### list()

List WhatsApp templates for a channel with optional pagination and search.

```typescript theme={null}
Templates.whatsapp.list(
  channelId: string,
  options?: ListTemplatesOptions
): Promise<PaginatedTemplatesResponse>
```

<ParamField path="channelId" type="string" required>
  The WhatsApp channel identifier
</ParamField>

<ParamField path="options" type="object">
  Optional pagination and search options
</ParamField>

<ParamField path="options.page" type="number" default={1}>
  Page number (1-indexed)
</ParamField>

<ParamField path="options.limit" type="number" default={10}>
  Items per page
</ParamField>

<ParamField path="options.search" type="string">
  Search query to filter templates by name
</ParamField>

**Returns:**

```typescript theme={null}
{
  templates: Template[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}
```

**Examples:**

```typescript theme={null}
// List all templates (first page)
const result = await Templates.whatsapp.list(channelId);

// With pagination
const page2 = await Templates.whatsapp.list(channelId, { page: 2, limit: 20 });

// Search by name
const orderTemplates = await Templates.whatsapp.list(channelId, { search: 'order' });

// Combine options
const filtered = await Templates.whatsapp.list(channelId, {
  page: 1,
  limit: 5,
  search: 'welcome'
});
```

### get()

Retrieve a specific WhatsApp template by ID.

```typescript theme={null}
Templates.whatsapp.get(
  channelId: string,
  templateId: string
): Promise<WhatsAppTemplate>
```

<ParamField path="channelId" type="string" required>
  The WhatsApp channel identifier
</ParamField>

<ParamField path="templateId" type="string" required>
  The template identifier
</ParamField>

**Returns:** `WhatsAppTemplate` object

**Example:**

```typescript theme={null}
const template = await Templates.whatsapp.get(channelId, 'template_123');

console.log(template.name);       // "order_confirmation"
console.log(template.status);     // "APPROVED"
console.log(template.category);   // "UTILITY"
console.log(template.language);   // "en"
console.log(template.components); // Array of components
```

### send()

Send a WhatsApp template message to one or more phone numbers.

```typescript theme={null}
Templates.whatsapp.send(
  channelId: string,
  templateId: string,
  data: SendTemplateData
): Promise<SendTemplateResponse>
```

<ParamField path="channelId" type="string" required>
  The WhatsApp channel identifier
</ParamField>

<ParamField path="templateId" type="string" required>
  The template identifier
</ParamField>

<ParamField path="data" type="object" required>
  Send data including recipients and template values
</ParamField>

<ParamField path="data.phoneNumbers" type="string[]" required>
  Array of recipients. For WhatsApp: phone numbers in E.164 format (e.g., +447551166594)
</ParamField>

<ParamField path="data.values" type="object">
  Template parameter values
</ParamField>

<ParamField path="data.values.header" type="object">
  Header component parameter values. For media headers, either use `image_url` (IMAGE format), `video_url` (VIDEO format), or `document_url` + optional `document_filename` (DOCUMENT format) — or pass a Meta media ID instead via `image_id`, `video_id`, or `document_id`. A media ID takes precedence if both are given.
</ParamField>

<ParamField path="data.values.body" type="object">
  Body component parameter values
</ParamField>

<ParamField path="data.values.buttons" type="SendTemplateButtonValue[]">
  Array of button parameter values. Each entry has `sub_type` (`'QUICK_REPLY'` | `'URL'` | `'PHONE_NUMBER'` | `'COPY_CODE'`), `index`, and optional `text` or `coupon_code`.
</ParamField>

**Returns:**

```typescript theme={null}
{
  results: Array<{
    phoneNumber: string;
    success: boolean;
    messageId?: string;
  }>;
  errors: Array<{
    phoneNumber: string;
    error: string;
  }>;
}
```

**Examples:**

```typescript theme={null}
// Simple send to one recipient
const result = await Templates.whatsapp.send(channelId, 'welcome_template', {
  phoneNumbers: ['+447551166594']
});

// Send with body parameters
const orderResult = await Templates.whatsapp.send(channelId, 'order_confirmation', {
  phoneNumbers: ['+447551166594'],
  values: {
    body: {
      first_name: 'John',
      order_number: '12345',
      delivery_date: 'December 25, 2025'
    }
  }
});

// Send to multiple recipients (batch)
const batchResult = await Templates.whatsapp.send(channelId, 'promotion', {
  phoneNumbers: ['+447551166594', '+447551166595', '+447551166596'],
  values: {
    header: { image_url: 'https://example.com/promo.jpg' },
    body: { discount: '20%' }
  }
});

// Send with video header
const videoResult = await Templates.whatsapp.send(channelId, 'product_demo', {
  phoneNumbers: ['+447551166594'],
  values: {
    header: { video_url: 'https://example.com/product-demo.mp4' },
    body: { product_name: 'Acme Widget' }
  }
});

// Send with document header
const docResult = await Templates.whatsapp.send(channelId, 'invoice', {
  phoneNumbers: ['+447551166594'],
  values: {
    header: {
      document_url: 'https://example.com/invoice-12345.pdf',
      document_filename: 'invoice-12345.pdf'
    },
    body: { order_number: '12345', total: '$99.99' }
  }
});

// Handle response
batchResult.results.forEach(r => {
  if (r.success) {
    console.log(`Sent to ${r.phoneNumber}: ${r.messageId}`);
  }
});

batchResult.errors.forEach(e => {
  console.error(`Failed for ${e.phoneNumber}: ${e.error}`);
});
```

## Template Structure

### WhatsApp Templates

WhatsApp templates have the following structure:

```typescript theme={null}
interface Template {
  id: string;
  name: string;
  status: 'APPROVED' | 'PENDING' | 'REJECTED';
  category: 'UTILITY' | 'MARKETING' | 'AUTHENTICATION';
  language: string;
  components: TemplateComponent[];
}

interface TemplateComponent {
  type: 'HEADER' | 'BODY' | 'FOOTER' | 'BUTTONS';
  format?: 'TEXT' | 'IMAGE' | 'VIDEO' | 'DOCUMENT';
  text?: string;
  buttons?: TemplateButton[];
}

interface TemplateButton {
  type: 'QUICK_REPLY' | 'URL' | 'PHONE_NUMBER' | 'COPY_CODE';
  text: string;
  url?: string;
  phone_number?: string;
  example?: string[];
}
```

<Note>
  Template structure may vary by template type. The above shows the WhatsApp template structure.
</Note>

### Media Headers

WhatsApp templates support media headers in addition to text headers. Headers can contain an image, video, or document:

* **IMAGE**: Static image (`format: 'IMAGE'`)
* **VIDEO**: Playable video clip (`format: 'VIDEO'`)
* **DOCUMENT**: Downloadable document like a PDF (`format: 'DOCUMENT'`)

When creating a template with a media header, you provide:

* **`format`** — one of `'IMAGE'`, `'VIDEO'`, or `'DOCUMENT'`
* **`mediaUrl`** — a publicly-reachable HTTPS URL to a sample file for approval. This URL must:
  * Be accessible over HTTPS
  * Match the format's MIME type requirements:
    * IMAGE: `.jpg`, `.png`, `.webp`
    * VIDEO: `.mp4`, `.3gp`
    * DOCUMENT: `.pdf`, `.docx`, `.xlsx`, `.pptx` (up to 10 MB)
  * Be within Meta's size limits (images ≤ 5 MB, videos ≤ 16 MB, documents ≤ 10 MB)

Once the template is approved, when you send it via the `Templates.whatsapp.send()` API, you provide the actual media URL in the `values.header` object:

* `image_url` for IMAGE format templates
* `video_url` for VIDEO format templates
* `document_url` (and optionally `document_filename`) for DOCUMENT format templates

The media URL provided at send time must also be publicly reachable over HTTPS.

### Sending by media ID instead of URL

WhatsApp re-downloads the URL on **every** send. For bulk campaigns you can upload the asset to Meta once and reuse the returned media ID across every recipient, which skips that per-send fetch entirely:

```typescript theme={null}
const response = await Templates.whatsapp.send('channel_123', 'promo_template', {
  phoneNumbers: ['+1234567890', '+0987654321'],
  values: {
    header: { image_id: '1234567890123456' },
    body: { discount: '20%' },
  },
});
```

Use `image_id`, `video_id`, or `document_id` to match the template's header format. `document_filename` still applies. If you pass both a media ID and a URL, the media ID wins.

<Warning>
  Three constraints decide whether a media ID resolves at send time:

  * **Upload it via `POST /{phone-number-id}/media`.** The Resumable Upload API (`POST /{app-id}/uploads`) returns an `h:...` file handle instead, which is only valid for *creating* a template — it will never work as a send-time media ID.
  * **Media IDs are scoped to the business phone number they were uploaded on.** That must be the phone number behind the channel you're sending from, or Meta won't resolve the ID.
  * **Meta expires media IDs after 30 days.** Plan on re-uploading per campaign rather than once forever.
</Warning>

## Template Parameters

Templates use named parameters in the format `{{parameter_name}}`. When sending, provide values for each parameter:

```typescript theme={null}
// Template body: "Hello {{first_name}}, your order {{order_number}} is ready!"

await Templates.whatsapp.send(channelId, templateId, {
  phoneNumbers: ['+447551166594'],
  values: {
    body: {
      first_name: 'John',
      order_number: 'ORD-12345'
    }
  }
});
```

<Warning>
  All required parameters must be provided when sending a template. Missing parameters will cause the send to fail.
</Warning>

## WhatsApp Examples

The following examples demonstrate using the Templates API with WhatsApp templates.

### Order Confirmation Tool

```typescript theme={null}
import { LuaTool, Templates } from 'lua-cli';
import { z } from 'zod';

export default class SendOrderConfirmationTool implements LuaTool {
  name = "send_order_confirmation";
  description = "Send order confirmation via WhatsApp template";
  
  inputSchema = z.object({
    channelId: z.string().describe("WhatsApp channel ID"),
    phoneNumber: z.string().describe("Customer phone number"),
    orderNumber: z.string().describe("Order number"),
    customerName: z.string().describe("Customer name"),
    deliveryDate: z.string().describe("Expected delivery date")
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const result = await Templates.whatsapp.send(input.channelId, 'order_confirmation', {
      phoneNumbers: [input.phoneNumber],
      values: {
        body: {
          customer_name: input.customerName,
          order_number: input.orderNumber,
          delivery_date: input.deliveryDate
        }
      }
    });
    
    if (result.errors.length > 0) {
      return {
        success: false,
        error: result.errors[0].error
      };
    }
    
    return {
      success: true,
      messageId: result.results[0].messageId,
      message: `Order confirmation sent to ${input.phoneNumber}`
    };
  }
}
```

### Webhook: Send Welcome Template on New User

```typescript theme={null}
import { LuaWebhook, LuaWebhookConfig, Templates } from 'lua-cli';

const config: LuaWebhookConfig = {
  name: 'new-user-welcome',
  description: 'Send welcome template when a new user signs up'
};

const webhook: LuaWebhook = {
  config,
  execute: async (event) => {
    const { channelId, phoneNumber, firstName } = event.data;
    
    // Find welcome template
    const listResult = await Templates.whatsapp.list(channelId, { search: 'welcome' });
    const welcomeTemplate = listResult.templates.find(t => t.name === 'welcome_message');
    
    if (!welcomeTemplate) {
      return { error: 'Welcome template not found' };
    }
    
    // Send welcome message
    const sendResult = await Templates.whatsapp.send(channelId, welcomeTemplate.id, {
      phoneNumbers: [phoneNumber],
      values: {
        body: { first_name: firstName }
      }
    });
    
    return {
      sent: sendResult.results.length > 0,
      messageId: sendResult.results[0]?.messageId
    };
  }
};

export default webhook;
```

### List Templates Tool

```typescript theme={null}
import { LuaTool, Templates } from 'lua-cli';
import { z } from 'zod';

export default class ListTemplatesTools implements LuaTool {
  name = "list_whatsapp_templates";
  description = "List available WhatsApp templates";
  
  inputSchema = z.object({
    channelId: z.string().describe("WhatsApp channel ID"),
    search: z.string().optional().describe("Search query to filter templates"),
    page: z.number().optional().describe("Page number")
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const result = await Templates.whatsapp.list(input.channelId, {
      search: input.search,
      page: input.page || 1,
      limit: 10
    });
    
    return {
      templates: result.templates.map(t => ({
        id: t.id,
        name: t.name,
        status: t.status,
        category: t.category,
        language: t.language
      })),
      total: result.total,
      page: result.page,
      totalPages: result.totalPages
    };
  }
}
```

## Use Cases (WhatsApp)

### Transactional Notifications

```typescript theme={null}
// Order shipped
await Templates.whatsapp.send(channelId, 'order_shipped', {
  phoneNumbers: [customerPhone],
  values: {
    body: {
      order_number: order.id,
      tracking_number: shipment.trackingNumber,
      carrier: shipment.carrier
    }
  }
});

// Appointment reminder
await Templates.whatsapp.send(channelId, 'appointment_reminder', {
  phoneNumbers: [patientPhone],
  values: {
    body: {
      patient_name: patient.name,
      appointment_date: appointment.date,
      doctor_name: appointment.doctor
    }
  }
});
```

### Marketing Campaigns

```typescript theme={null}
// Get all customers who opted in
const customers = await getOptedInCustomers();

// Send promotion to all
const result = await Templates.whatsapp.send(channelId, 'holiday_sale', {
  phoneNumbers: customers.map(c => c.phone),
  values: {
    header: { image_url: 'https://example.com/sale-banner.jpg' },
    body: { discount_code: 'HOLIDAY25' }
  }
});

console.log(`Sent: ${result.results.length}, Failed: ${result.errors.length}`);
```

### Authentication / OTP

```typescript theme={null}
// Send OTP
const otp = generateOTP();
await Templates.whatsapp.send(channelId, 'otp_verification', {
  phoneNumbers: [userPhone],
  values: {
    body: { otp_code: otp }
  }
});
```

## Tracking Delivery Status

After sending template messages, you can track whether they were delivered, read, or failed using webhook event subscriptions.

### Setup

1. Create a webhook to handle delivery events
2. Subscribe it to the status events you care about

```bash theme={null}
lua webhooks subscribe --webhook-name campaign-tracker --event message.sent
lua webhooks subscribe --webhook-name campaign-tracker --event message.delivered
lua webhooks subscribe --webhook-name campaign-tracker --event message.read
lua webhooks subscribe --webhook-name campaign-tracker --event message.failed
```

### Example: Campaign Analytics

```typescript theme={null}
import { LuaWebhook, Data, Templates } from 'lua-cli';

const campaignTracker = new LuaWebhook({
  name: 'campaign-tracker',
  description: 'Track template message delivery for campaigns',
  
  execute: async (event) => {
    const { body } = event;
    
    await Data.create('campaign-metrics', {
      messageId: body.messageWamid,
      recipient: body.recipientId,
      status: body.status,
      timestamp: body.timestamp,
      billable: body.pricing?.billable
    }, `${body.status} ${body.recipientId}`);
    
    return { tracked: true };
  }
});

export default campaignTracker;
```

After sending a batch of templates, your webhook receives individual status updates for each recipient as messages are sent, delivered, and read. Use this data to measure open rates, delivery rates, and catch failures.

<Note>
  The `messageId` returned by `Templates.whatsapp.send()` corresponds to `messageWamid` in the status event payload, allowing you to correlate sends with delivery outcomes.
</Note>

## Best Practices

<AccordionGroup>
  <Accordion title="Handle Batch Send Results">
    When sending to multiple recipients, always check for partial failures:

    ```typescript theme={null}
    const result = await Templates.whatsapp.send(channelId, templateId, {
      phoneNumbers: phoneNumbers
    });

    // Log successes
    result.results.forEach(r => {
      console.log(`✓ Sent to ${r.phoneNumber}`);
    });

    // Handle failures
    if (result.errors.length > 0) {
      result.errors.forEach(e => {
        console.error(`✗ Failed for ${e.phoneNumber}: ${e.error}`);
      });
    }
    ```
  </Accordion>

  <Accordion title="Validate Phone Numbers">
    Use E.164 format for phone numbers:

    ```typescript theme={null}
    // ✅ Good
    phoneNumbers: ['+447551166594', '+14155552671']

    // ❌ May cause issues
    phoneNumbers: ['07551166594', '(415) 555-2671']
    ```
  </Accordion>

  <Accordion title="Check Template Status">
    Only send approved templates:

    ```typescript theme={null}
    const template = await Templates.whatsapp.get(channelId, templateId);

    if (template.status !== 'APPROVED') {
      throw new Error(`Template ${template.name} is not approved (status: ${template.status})`);
    }
    ```
  </Accordion>

  <Accordion title="Match Parameter Names Exactly">
    Parameter names in `values` must match template parameters exactly:

    ```typescript theme={null}
    // If template has: "Hello {{first_name}}, order {{order_id}} is ready"

    // ✅ Correct
    values: {
      body: {
        first_name: 'John',
        order_id: '12345'
      }
    }

    // ❌ Wrong parameter names
    values: {
      body: {
        name: 'John',      // Should be 'first_name'
        orderId: '12345'   // Should be 'order_id'
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## TypeScript Types

```typescript theme={null}
interface ListTemplatesOptions {
  page?: number;
  limit?: number;
  search?: string;
}

interface PaginatedTemplatesResponse {
  templates: Template[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

// WhatsApp template structure
interface WhatsAppTemplate {
  id: string;
  name: string;
  status: 'APPROVED' | 'PENDING' | 'REJECTED';
  category: 'UTILITY' | 'MARKETING' | 'AUTHENTICATION';
  language: string;
  components: TemplateComponent[];
  correct_category?: string;
  message_send_ttl_seconds?: number;
  parameter_format?: 'NAMED' | 'POSITIONAL';
  previous_category?: string;
  rejected_reason?: string;
}

interface SendTemplateData {
  phoneNumbers: string[];  // Recipients (phone numbers for WhatsApp)
  values?: {
    header?: Record<string, string>;
    body?: Record<string, string>;
    buttons?: SendTemplateButtonValue[];
  };
}

interface SendTemplateButtonValue {
  sub_type: 'QUICK_REPLY' | 'URL' | 'PHONE_NUMBER' | 'COPY_CODE';
  index: string;
  text?: string;
  coupon_code?: string;
}

interface SendTemplateResponse {
  results: Array<{
    phoneNumber: string;
    success: boolean;
    messageId?: string;
  }>;
  errors: Array<{
    phoneNumber: string;
    error: string;
  }>;
  totalProcessed: number;
  totalErrors: number;
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="User API" icon="user" href="/api/user">
    Send messages to users
  </Card>

  <Card title="Jobs API" icon="clock" href="/api/jobs">
    Schedule template sends
  </Card>
</CardGroup>
