DEV Community

Kyle White
Kyle White

Posted on • Originally published at clipspeed.ai

Automating Social Media Clip Distribution: Node.js, BullMQ, and Platform APIs

If you manage a content channel that produces clips from long-form video, manual social media posting is the first thing that should be automated. But most tutorials cover Buffer and Hootsuite — tools built for marketers. This post is for developers who want to build a clip distribution pipeline that routes content programmatically across platforms.

We'll cover queue architecture, platform API integration patterns, and how ClipSpeedAI handles the upstream clip generation that feeds this kind of pipeline.

The Core Architecture Problem

The naïve approach is a cron job that loops through a list of clips and posts them. This breaks immediately at scale because:

  • Platform APIs have rate limits (TikTok: 100 posts/day, YouTube: 6 uploads/day on new accounts)
  • You need retry logic with exponential backoff
  • Failed posts need a dead-letter queue for review
  • You need per-platform credential management

The production pattern is a message queue with typed job processors per platform.

Queue Setup with BullMQ + Redis

import { Queue, Worker } from 'bullmq';
import { Redis } from 'ioredis';

const connection = new Redis({ maxRetriesPerRequest: null });

const clipQueue = new Queue('clip-distribution', { connection });

// Add a clip to be distributed
await clipQueue.add('post-clip', {
  clipId: 'clip_abc123',
  platforms: ['youtube_shorts', 'tiktok'],
  title: 'Best moment from today\'s stream',
  filePath: '/tmp/clips/clip_abc123.mp4',
  scheduledFor: new Date('2026-04-20T14:00:00Z').getTime()
}, {
  delay: Date.now() - new Date('2026-04-20T14:00:00Z').getTime(),
  attempts: 3,
  backoff: { type: 'exponential', delay: 5000 }
});
Enter fullscreen mode Exit fullscreen mode

Platform-Specific Workers

Each platform gets its own worker because their APIs differ significantly:

// YouTube Shorts worker
const youtubeWorker = new Worker('clip-distribution', async (job) => {
  if (!job.data.platforms.includes('youtube_shorts')) return;

  const { youtube } = await initGoogleClient(job.data.userId);

  const res = await youtube.videos.insert({
    part: ['snippet', 'status'],
    requestBody: {
      snippet: { title: job.data.title, categoryId: '22' },
      status: { privacyStatus: 'public', selfDeclaredMadeForKids: false }
    },
    media: { body: fs.createReadStream(job.data.filePath) }
  });

  return { youtubeId: res.data.id, url: `https://youtube.com/shorts/${res.data.id}` };
}, { connection, concurrency: 2 });

// Handle failures
youtubeWorker.on('failed', (job, err) => {
  console.error(`YouTube post failed for ${job.data.clipId}: ${err.message}`);
});
Enter fullscreen mode Exit fullscreen mode

Scheduling at Peak Engagement Times

Rather than posting immediately, build a time-slot allocator that distributes posts across peak engagement windows:

const PEAK_WINDOWS = {
  youtube_shorts: [{ hour: 12, days: [1,2,3,4,5] }, { hour: 17, days: [1,2,3,4,5] }],
  tiktok: [{ hour: 9, days: [0,1,2,3,4,5,6] }, { hour: 19, days: [0,1,2,3,4,5,6] }]
};

function nextSlot(platform, tz = 'America/New_York') {
  const windows = PEAK_WINDOWS[platform];
  const now = new Date();
  // Find next available slot that isn't already saturated
  // (implementation depends on your slot saturation tracking)
  return computeNextAvailableSlot(windows, now, tz);
}
Enter fullscreen mode Exit fullscreen mode

Handling Platform API Errors

Platform APIs fail in predictable ways:

Error Meaning Action
401 Token expired Refresh OAuth token, retry
429 Rate limited Respect Retry-After header
400 on file Encoding mismatch Re-encode with platform-specific ffmpeg preset
503 Platform outage Dead-letter queue, alert
youtubeWorker.on('failed', async (job, err) => {
  if (err.code === 401) {
    await refreshToken(job.data.userId, 'youtube');
    await job.retry();
  } else if (err.code === 429) {
    const retryAfter = parseInt(err.headers?.['retry-after'] || '60') * 1000;
    await job.moveToDelayed(Date.now() + retryAfter);
  } else {
    await moveToDeadLetter(job, err);
  }
});
Enter fullscreen mode Exit fullscreen mode

Platform-Specific ffmpeg Presets

Each platform has encoding requirements. Encoding clips correctly upstream avoids API rejections:

# YouTube Shorts — H.264, 9:16, max 60s
ffmpeg -i input.mp4 -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920"   -c:v libx264 -crf 23 -preset fast -c:a aac -b:a 128k   -t 59 output_yt_short.mp4

# TikTok — same codec, stricter filesize
ffmpeg -i input.mp4 -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920"   -c:v libx264 -crf 26 -b:v 2M -c:a aac -b:a 128k   -t 59 output_tiktok.mp4
Enter fullscreen mode Exit fullscreen mode

Integrating ClipSpeedAI as the Upstream Source

The distribution pipeline only works if you have quality clips to distribute. ClipSpeedAI handles the detection and clip generation step — viral moment detection, speaker tracking, animated captions — and outputs ready-to-distribute MP4s. See all ClipSpeedAI features for details on the clip output format and caption rendering.

Full scheduling automation guide: ClipSpeedAI blog.

Summary

A production clip distribution pipeline needs: BullMQ for scheduling, per-platform workers with proper error handling, platform-specific encoding presets, and a dead-letter queue for failures. The upstream source — ClipSpeedAI — handles clip generation, so your distribution layer just needs to route finished MP4s to the right platforms at the right times.

Try ClipSpeedAI free — no card required.

Top comments (0)