convoup Docs
Docs / Guides / Bulk Sending

Bulk Sending

For sending templates to many customers, iterate over your customer list with per-customer error handling.

Basic Bulk Send

TypeScript
import { Convoup, ConvoupError } from 'convoup';

const client = new Convoup({
  apiKey: process.env.CONVOUP_API_KEY!,
});

const customers = [
  { phone: '+918851479441', invoiceId: 'INV-001', amount: 'Rs. 2,500', pdfUrl: 'https://api.example.com/invoices/INV-001.pdf' },
  { phone: '+919876543210', invoiceId: 'INV-002', amount: 'Rs. 4,800', pdfUrl: 'https://api.example.com/invoices/INV-002.pdf' },
  // ... 498 more customers
];

let sent = 0;
let failed = 0;

for (const customer of customers) {
  try {
    await client.sendInvoice({
      to: customer.phone,
      template: 'test_welcome_template',
      params: [customer.invoiceId, customer.amount],
      pdfUrl: customer.pdfUrl,
    });
    sent++;
  } catch (err) {
    failed++;
    if (err instanceof ConvoupError) {
      console.error(`Failed for ${customer.phone}: ${err.code} - ${err.message}`);
      if (err.code === 'TEMPLATE_NOT_FOUND') {
        console.error('Template not found. Aborting batch.');
        break;
      }
    } else {
      console.error(`Unexpected error for ${customer.phone}:`, err);
    }
  }
}

console.log(`Done. Sent: ${sent}, Failed: ${failed}`);

Bulk Send with Rate Limiting

Add delays between sends to avoid hitting rate limits:

TypeScript
function delay(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

for (const customer of customers) {
  try {
    await client.sendInvoice({
      to: customer.phone,
      template: 'test_welcome_template',
      params: [customer.invoiceId, customer.amount],
      pdfUrl: customer.pdfUrl,
    });
    sent++;
  } catch (err) {
    failed++;
    if (err instanceof ConvoupError && err.code === 'TEMPLATE_NOT_FOUND') break;
  }

  // 100ms delay between sends
  await delay(100);
}
Rate limit numbers not specified
The SDK documentation suggests 100-500ms delays as example guidance, not a documented contract. Actual rate limits depend on your WABA tier and Meta's policies. Start with conservative delays and adjust based on 429 responses.

Bulk Send with Concurrency Control

For large batches, send multiple messages in parallel with a concurrency limit:

TypeScript
async function bulkSendWithConcurrency(
  customers: Customer[],
  concurrency: number,
): Promise<{ sent: number; failed: number }> {
  let sent = 0;
  let failed = 0;

  const chunks: Customer[][] = [];
  for (let i = 0; i < customers.length; i += concurrency) {
    chunks.push(customers.slice(i, i + concurrency));
  }

  for (const chunk of chunks) {
    const results = await Promise.allSettled(
      chunk.map(c =>
        client.sendInvoice({
          to: c.phone,
          template: 'test_welcome_template',
          params: [c.invoiceId, c.amount],
          pdfUrl: c.pdfUrl,
        })
      )
    );

    for (const result of results) {
      if (result.status === 'fulfilled') sent++;
      else failed++;
    }

    // Delay between chunks
    await delay(500);
  }

  return { sent, failed };
}

const results = await bulkSendWithConcurrency(customers, 10);
console.log(`Sent: ${results.sent}, Failed: ${results.failed}`);

Concurrency Flow

graph TD A[Customer List] --> B[Split into chunks] B --> C[Process chunk with Promise.allSettled] C --> D{More chunks?} D -->|Yes| E[Delay between chunks] E --> C D -->|No| F[Return sent/failed counts]

Error Handling Strategy

Error CodeAction
TEMPLATE_NOT_FOUNDAbort the entire batch - template name is wrong
TEMPLATE_PARAM_MISMATCHLog and continue - params don't match for this customer
RECIPIENT_SUPPRESSEDLog and continue - number is blocked
RATE_LIMITEDDelay and retry
Network errorLog and continue - may be transient

Next Steps