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

# Bulk Generation

> Generate hundreds of personalized videos at scale

## Overview

Bulk generation allows you to create hundreds or thousands of personalized videos efficiently using templates and automation.

<Info>
  **Use Case**: Perfect for personalized marketing campaigns, sales outreach, customer onboarding, and more.
</Info>

## Prerequisites

Before bulk generating videos:

<Check>**You need:**</Check>

* A template with defined variables
* A list of recipients with their data
* A webhook endpoint to receive completion notifications
* Sufficient API credits/quota

## Basic Bulk Generation

### Step 1: Prepare Your Data

```javascript theme={null}
// recipients.json
const recipients = [
  {
    name: "Sarah Johnson",
    company: "TechCorp",
    role: "CEO",
    metric: "30% increase in productivity"
  },
  {
    name: "Mike Chen",
    company: "StartupXYZ",
    role: "CTO",
    metric: "50% faster deployment"
  },
  {
    name: "Emma Davis",
    company: "BigCorp Inc",
    role: "VP of Sales",
    metric: "$2M in additional revenue"
  }
  // ... more recipients
];
```

### Step 2: Simple Bulk Generator

```javascript theme={null}
async function bulkGenerate(templateId, recipients) {
  const results = [];
  
  for (const recipient of recipients) {
    try {
      const response = await fetch('https://api.hooked.so/v1/project/create', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': process.env.HOOKED_API_KEY
        },
        body: JSON.stringify({
          type: 'class',
          templateId: templateId,
          title: `Video for ${recipient.name}`,
          scenes: [{
            script: template.scenes[0].script,
            variables: Object.entries(recipient).map(([key, value]) => ({
              key,
              value: String(value)
            }))
          }],
          webhook: `https://yoursite.com/webhook?recipient=${recipient.email}`
        })
      });
      
      const result = await response.json();
      
      if (result.success) {
        results.push({
          recipient: recipient.name,
          projectId: result.data.projectId,
          status: 'queued'
        });
        console.log(`✅ Queued video for ${recipient.name}`);
      } else {
        throw new Error(result.message);
      }
      
      // Rate limiting: wait 1 second between requests
      await new Promise(r => setTimeout(r, 1000));
      
    } catch (error) {
      console.error(`❌ Failed for ${recipient.name}:`, error.message);
      results.push({
        recipient: recipient.name,
        status: 'failed',
        error: error.message
      });
    }
  }
  
  return results;
}

// Execute
const results = await bulkGenerate('template_123', recipients);
console.log(`Generated ${results.filter(r => r.status === 'queued').length}/${recipients.length} videos`);
```

## Advanced Bulk Generation

### With Rate Limiting & Retries

```javascript theme={null}
class BulkVideoGenerator {
  constructor(apiKey, options = {}) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://api.hooked.so/v1';
    this.concurrency = options.concurrency || 5;
    this.retryAttempts = options.retryAttempts || 3;
    this.retryDelay = options.retryDelay || 5000;
  }
  
  async generateOne(templateId, recipient, attempt = 1) {
    try {
      const response = await fetch(`${this.baseUrl}/project/create`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': this.apiKey
        },
        body: JSON.stringify({
          type: 'class',
          templateId,
          title: `Video for ${recipient.name}`,
          scenes: [{
            variables: Object.entries(recipient).map(([key, value]) => ({
              key,
              value: String(value)
            }))
          }],
          webhook: recipient.webhook
        })
      });
      
      const result = await response.json();
      
      if (!response.ok) {
        // Retry on rate limit
        if (response.status === 429 && attempt < this.retryAttempts) {
          const retryAfter = response.headers.get('Retry-After') || this.retryDelay / 1000;
          console.log(`Rate limited. Retrying in ${retryAfter}s...`);
          await new Promise(r => setTimeout(r, retryAfter * 1000));
          return this.generateOne(templateId, recipient, attempt + 1);
        }
        
        throw new Error(result.message);
      }
      
      return {
        success: true,
        recipient: recipient.name,
        projectId: result.data.projectId
      };
      
    } catch (error) {
      if (attempt < this.retryAttempts) {
        console.log(`Retry ${attempt}/${this.retryAttempts} for ${recipient.name}`);
        await new Promise(r => setTimeout(r, this.retryDelay));
        return this.generateOne(templateId, recipient, attempt + 1);
      }
      
      return {
        success: false,
        recipient: recipient.name,
        error: error.message
      };
    }
  }
  
  async generateBulk(templateId, recipients, onProgress) {
    const results = [];
    const batches = [];
    
    // Split into batches based on concurrency
    for (let i = 0; i < recipients.length; i += this.concurrency) {
      batches.push(recipients.slice(i, i + this.concurrency));
    }
    
    let completed = 0;
    
    for (const batch of batches) {
      const batchResults = await Promise.all(
        batch.map(recipient => this.generateOne(templateId, recipient))
      );
      
      results.push(...batchResults);
      completed += batch.length;
      
      if (onProgress) {
        onProgress({
          completed,
          total: recipients.length,
          percentage: Math.round((completed / recipients.length) * 100)
        });
      }
      
      // Wait between batches to respect rate limits
      if (batches.indexOf(batch) < batches.length - 1) {
        await new Promise(r => setTimeout(r, 2000));
      }
    }
    
    return results;
  }
  
  getStats(results) {
    return {
      total: results.length,
      successful: results.filter(r => r.success).length,
      failed: results.filter(r => !r.success).length,
      successRate: Math.round((results.filter(r => r.success).length / results.length) * 100)
    };
  }
}

// Usage
const generator = new BulkVideoGenerator(process.env.HOOKED_API_KEY, {
  concurrency: 5,
  retryAttempts: 3,
  retryDelay: 5000
});

const results = await generator.generateBulk(
  'template_123',
  recipients,
  (progress) => {
    console.log(`Progress: ${progress.completed}/${progress.total} (${progress.percentage}%)`);
  }
);

const stats = generator.getStats(results);
console.log('Generation complete:', stats);
```

## Processing CSV Files

### Read and Process CSV

```javascript theme={null}
import fs from 'fs';
import csv from 'csv-parser';

async function processCSV(filePath, templateId) {
  const recipients = [];
  
  return new Promise((resolve, reject) => {
    fs.createReadStream(filePath)
      .pipe(csv())
      .on('data', (row) => {
        // Validate required fields
        if (row.name && row.email) {
          recipients.push({
            name: row.name,
            email: row.email,
            company: row.company || 'Unknown',
            role: row.role || 'Professional',
            // Add more fields as needed
            webhook: `https://yoursite.com/webhook?email=${row.email}`
          });
        }
      })
      .on('end', async () => {
        console.log(`Loaded ${recipients.length} recipients from CSV`);
        
        const generator = new BulkVideoGenerator(process.env.HOOKED_API_KEY);
        const results = await generator.generateBulk(templateId, recipients);
        
        resolve(results);
      })
      .on('error', reject);
  });
}

// Usage
const results = await processCSV('./recipients.csv', 'template_123');
```

## Database Integration

### Generate from Database Records

```javascript theme={null}
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function generateFromDatabase(templateId, filters = {}) {
  // Fetch recipients from database
  const contacts = await prisma.contact.findMany({
    where: {
      status: 'active',
      hasConsent: true,
      ...filters
    },
    select: {
      id: true,
      name: true,
      email: true,
      company: true,
      customFields: true
    }
  });
  
  console.log(`Found ${contacts.length} contacts in database`);
  
  const generator = new BulkVideoGenerator(process.env.HOOKED_API_KEY);
  
  const results = await generator.generateBulk(
    templateId,
    contacts.map(c => ({
      ...c,
      ...c.customFields,
      webhook: `https://yoursite.com/webhook?contactId=${c.id}`
    })),
    async (progress) => {
      // Update progress in database
      await prisma.bulkJob.update({
        where: { id: jobId },
        data: {
          progress: progress.percentage,
          completed: progress.completed
        }
      });
    }
  );
  
  // Save results to database
  for (const result of results) {
    if (result.success) {
      await prisma.video.create({
        data: {
          projectId: result.projectId,
          contactId: result.recipient.id,
          status: 'processing'
        }
      });
    }
  }
  
  return results;
}

const results = await generateFromDatabase('template_123', {
  segment: 'enterprise'
});
```

## Webhook Management for Bulk Jobs

### Track Completions

```javascript theme={null}
// In-memory tracking (use Redis/Database for production)
const bulkJobs = new Map();

// Start bulk job
app.post('/api/bulk/start', async (req, res) => {
  const { templateId, recipients } = req.body;
  const jobId = `job_${Date.now()}`;
  
  bulkJobs.set(jobId, {
    id: jobId,
    total: recipients.length,
    completed: 0,
    videos: [],
    startedAt: new Date()
  });
  
  // Start generation in background
  generateBulkAsync(jobId, templateId, recipients);
  
  res.json({ jobId, status: 'started' });
});

// Webhook handler
app.post('/api/webhook', async (req, res) => {
  const { projectId, status, video } = req.body;
  const jobId = req.query.jobId;
  
  if (jobId && bulkJobs.has(jobId)) {
    const job = bulkJobs.get(jobId);
    
    job.completed++;
    if (status === 'completed') {
      job.videos.push({
        projectId,
        url: video.url,
        completedAt: new Date()
      });
    }
    
    // Check if job complete
    if (job.completed === job.total) {
      job.completedAt = new Date();
      job.duration = job.completedAt - job.startedAt;
      
      console.log(`Bulk job ${jobId} completed!`);
      console.log(`Generated ${job.videos.length}/${job.total} videos in ${job.duration}ms`);
      
      // Notify user
      await notifyJobComplete(jobId, job);
    }
    
    bulkJobs.set(jobId, job);
  }
  
  res.status(200).send('OK');
});

// Check job status
app.get('/api/bulk/:jobId', (req, res) => {
  const job = bulkJobs.get(req.params.jobId);
  
  if (!job) {
    return res.status(404).json({ error: 'Job not found' });
  }
  
  res.json({
    ...job,
    progress: Math.round((job.completed / job.total) * 100),
    status: job.completedAt ? 'completed' : 'processing'
  });
});
```

## Performance Optimization

### Tips for Large Batches

<Steps>
  <Step title="Use Concurrency">
    Process multiple videos in parallel (5-10 concurrent requests)

    ```javascript theme={null}
    const concurrency = 5;
    ```
  </Step>

  <Step title="Implement Queuing">
    Use a job queue (Bull, BullMQ) for reliability

    ```javascript theme={null}
    import Queue from 'bull';

    const videoQueue = new Queue('videos', {
      redis: { host: 'localhost', port: 6379 }
    });

    videoQueue.process(async (job) => {
      return await generateVideo(job.data);
    });
    ```
  </Step>

  <Step title="Cache Templates">
    Fetch template details once and reuse

    ```javascript theme={null}
    const template = await getTemplate(templateId);
    // Reuse for all recipients
    ```
  </Step>

  <Step title="Monitor Rate Limits">
    Track and respect API rate limits

    ```javascript theme={null}
    const rateLimiter = new RateLimiter(100, '1h');
    ```
  </Step>
</Steps>

## Error Handling Strategies

### Partial Failure Recovery

```javascript theme={null}
async function generateWithRecovery(templateId, recipients) {
  const results = {
    successful: [],
    failed: [],
    pending: [...recipients]
  };
  
  // Save state to disk/database
  const saveState = () => {
    fs.writeFileSync('bulk-state.json', JSON.stringify(results));
  };
  
  for (const recipient of recipients) {
    try {
      const projectId = await generateOne(templateId, recipient);
      results.successful.push({ recipient, projectId });
      results.pending = results.pending.filter(r => r !== recipient);
      
    } catch (error) {
      results.failed.push({ recipient, error: error.message });
      results.pending = results.pending.filter(r => r !== recipient);
    }
    
    // Save progress
    saveState();
  }
  
  return results;
}

// Resume from saved state
function resumeBulkGeneration() {
  const state = JSON.parse(fs.readFileSync('bulk-state.json'));
  return generateWithRecovery(templateId, state.pending);
}
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Start Small" icon="seedling">
    Test with 5-10 videos before scaling to hundreds
  </Card>

  <Card title="Use Webhooks" icon="webhook">
    Never poll for bulk jobs - always use webhooks
  </Card>

  <Card title="Monitor Progress" icon="chart-line">
    Track success rates and errors in real-time
  </Card>

  <Card title="Implement Retries" icon="rotate">
    Handle transient failures with exponential backoff
  </Card>
</CardGroup>

## Example: Complete Bulk Campaign

```javascript theme={null}
// complete-campaign.js
import { BulkVideoGenerator } from './bulk-generator';
import { sendEmail } from './email-service';

async function runCampaign(campaignId) {
  // 1. Load recipients from database
  const recipients = await db.recipients.findMany({
    where: { campaignId, status: 'pending' }
  });
  
  // 2. Get template
  const campaign = await db.campaign.findUnique({
    where: { id: campaignId },
    include: { template: true }
  });
  
  // 3. Generate videos
  const generator = new BulkVideoGenerator(process.env.HOOKED_API_KEY);
  
  const results = await generator.generateBulk(
    campaign.template.id,
    recipients.map(r => ({
      ...r,
      webhook: `https://mysite.com/webhook?recipientId=${r.id}&campaignId=${campaignId}`
    })),
    async (progress) => {
      // Update campaign progress
      await db.campaign.update({
        where: { id: campaignId },
        data: { progress: progress.percentage }
      });
    }
  );
  
  // 4. Log results
  const stats = generator.getStats(results);
  await db.campaign.update({
    where: { id: campaignId },
    data: {
      status: 'completed',
      stats: stats,
      completedAt: new Date()
    }
  });
  
  // 5. Notify admin
  await sendEmail({
    to: campaign.owner.email,
    subject: `Campaign ${campaign.name} completed`,
    text: `Generated ${stats.successful}/${stats.total} videos (${stats.successRate}% success rate)`
  });
  
  return results;
}

// Start campaign
runCampaign('campaign_123');
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Personalization Tips" icon="sparkles" href="/guides/personalization">
    Advanced personalization techniques
  </Card>

  <Card title="Best Practices" icon="star" href="/guides/best-practices">
    Production-ready patterns
  </Card>
</CardGroup>
