Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/darkzOGx/youtube-automation-agent/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Publishing & Scheduling Agent is your automated publishing manager that handles the entire video upload process, optimizes publishing times based on channel analytics, and manages your content calendar. It ensures your videos are published at optimal times for maximum reach and engagement.

What It Does

The Publishing & Scheduling Agent manages the publish queue, uploads videos to YouTube with all metadata, schedules content for optimal times, and provides publishing analytics and performance tracking.

Key Features

Automated Uploads

Handles complete video upload including metadata, thumbnails, and captions

Smart Scheduling

Optimizes publish times based on channel analytics

Queue Management

Manages publishing queue with priority handling

Publishing Reports

Tracks publishing performance and schedule accuracy

Core Methods

scheduleContent()

Adds content to the publishing queue with optimal timing.
publishing-scheduling-agent.js
async scheduleContent(productionData) {
  this.logger.info(`Scheduling content: ${productionData.id}`);
  
  const scheduleEntry = {
    productionId: productionData.id,
    title: productionData.script.title,
    publishTime: productionData.scheduledPublishTime,
    status: 'scheduled',
    priority: productionData.priority,
    metadata: {
      seo: productionData.seo,
      thumbnail: productionData.assets.thumbnail,
      video: productionData.assets.finalVideo,
      captions: productionData.assets.captions
    },
    createdAt: new Date().toISOString()
  };
  
  this.publishQueue.push(scheduleEntry);
  this.publishQueue.sort((a, b) => 
    new Date(a.publishTime) - new Date(b.publishTime)
  );
  
  await this.db.saveScheduleEntry(scheduleEntry);
  
  this.logger.info(`Content scheduled for: ${scheduleEntry.publishTime}`);
  return scheduleEntry;
}

publishContent()

Handles the complete video upload process to YouTube.
publishing-scheduling-agent.js
async publishContent(contentId) {
  this.logger.info(`Publishing content: ${contentId}`);
  
  const scheduleEntry = this.publishQueue.find(entry => 
    entry.productionId === contentId || entry.id === contentId
  );
  
  if (!scheduleEntry) {
    throw new Error(`Content not found in queue: ${contentId}`);
  }
  
  // Upload video to YouTube
  const uploadResult = await this.uploadToYouTube(scheduleEntry);
  
  // Update database
  scheduleEntry.status = 'published';
  scheduleEntry.publishedAt = new Date().toISOString();
  scheduleEntry.youtubeId = uploadResult.id;
  scheduleEntry.youtubeUrl = `https://www.youtube.com/watch?v=${uploadResult.id}`;
  
  await this.db.updateScheduleEntry(scheduleEntry);
  
  // Remove from queue
  this.publishQueue = this.publishQueue.filter(entry => 
    entry.id !== scheduleEntry.id
  );
  
  this.logger.success(`Content published: ${scheduleEntry.youtubeUrl}`);
  return scheduleEntry;
}

uploadToYouTube()

Handles the YouTube API upload with all metadata.
publishing-scheduling-agent.js
async uploadToYouTube(scheduleEntry) {
  const { metadata } = scheduleEntry;
  
  // Prepare video metadata
  const videoMetadata = {
    snippet: {
      title: metadata.seo.title,
      description: metadata.seo.description,
      tags: metadata.seo.tags,
      categoryId: metadata.seo.metadata.category.toString(),
      defaultLanguage: metadata.seo.metadata.language,
      defaultAudioLanguage: metadata.seo.metadata.language
    },
    status: {
      privacyStatus: process.env.DEFAULT_PRIVACY_STATUS || 'public',
      publishAt: scheduleEntry.publishTime,
      selfDeclaredMadeForKids: false
    }
  };
  
  // Upload video file
  const videoUpload = await this.youtube.videos.insert({
    part: 'snippet,status',
    requestBody: videoMetadata,
    media: {
      body: await this.getVideoStream(metadata.video.path)
    }
  });
  
  const videoId = videoUpload.data.id;
  this.logger.info(`Video uploaded with ID: ${videoId}`);
  
  // Upload thumbnail
  if (metadata.thumbnail && metadata.thumbnail.path) {
    await this.uploadThumbnail(videoId, metadata.thumbnail.path);
  }
  
  // Upload captions
  if (metadata.captions && metadata.captions.path) {
    await this.uploadCaptions(videoId, metadata.captions.path);
  }
  
  return videoUpload.data;
}

Publishing Workflow

1

Content Enters Queue

Production agent adds completed content to publish queue
2

Time Optimization

Agent analyzes optimal publish time based on channel analytics
3

Priority Assignment

Content is prioritized based on trend sensitivity and estimated impact
4

Automatic Publishing

At scheduled time, agent uploads video with all metadata
5

Thumbnail Upload

Custom thumbnail is uploaded after video processing
6

Caption Upload

Generated captions are added for accessibility
7

Status Tracking

Publishing success is recorded and analytics tracking begins

Publish Time Optimization

The agent analyzes channel data to find optimal publishing times:

optimizePublishTimes()

publishing-scheduling-agent.js
async optimizePublishTimes() {
  // Analyze channel analytics to find optimal publish times
  const analytics = await this.getChannelAnalytics();
  const optimalTimes = this.calculateOptimalTimes(analytics);
  
  // Update scheduled content with better times
  for (const entry of this.publishQueue) {
    if (entry.status === 'scheduled') {
      const currentTime = new Date(entry.publishTime);
      const betterTime = this.findBetterTime(currentTime, optimalTimes);
      
      if (betterTime && betterTime.getTime() !== currentTime.getTime()) {
        entry.publishTime = betterTime.toISOString();
        await this.db.updateScheduleEntry(entry);
        this.logger.info(`Optimized publish time for: ${entry.title}`);
      }
    }
  }
}

findBetterTime()

publishing-scheduling-agent.js
findBetterTime(currentTime, optimalTimes) {
  const currentDay = currentTime.toLocaleDateString('en-US', { weekday: 'long' });
  const currentHour = currentTime.getHours();
  
  // If current time is already optimal, return null
  if (optimalTimes.bestDays.includes(currentDay) && 
      optimalTimes.bestHours.includes(currentHour)) {
    return null;
  }
  
  // Find the next optimal time
  const nextOptimalTime = new Date(currentTime);
  
  // Try to find an optimal hour on the same day
  for (const hour of optimalTimes.bestHours) {
    if (hour > currentHour) {
      nextOptimalTime.setHours(hour, 0, 0, 0);
      if (optimalTimes.bestDays.includes(currentDay)) {
        return nextOptimalTime;
      }
    }
  }
  
  // Find next optimal day
  for (let i = 1; i <= 7; i++) {
    const testDate = new Date(currentTime.getTime() + (i * 24 * 60 * 60 * 1000));
    const testDay = testDate.toLocaleDateString('en-US', { weekday: 'long' });
    
    if (optimalTimes.bestDays.includes(testDay)) {
      testDate.setHours(optimalTimes.bestHours[0], 0, 0, 0);
      return testDate;
    }
  }
  
  return null;
}
The agent analyzes your channel’s historical performance to identify when your audience is most active and engaged.

Optimal Publishing Times

Based on YouTube analytics research:
Tuesday - Thursday: Peak engagement daysSaturday - Sunday: Strong weekend traffic
optimalDays: ['Tuesday', 'Wednesday', 'Thursday']

Queue Management

The publishing queue supports advanced management:

processPublishQueue()

Automatically processes scheduled content:
publishing-scheduling-agent.js
async processPublishQueue() {
  this.logger.info('Processing publish queue...');
  
  const now = new Date();
  const readyToPublish = this.publishQueue.filter(entry => {
    const publishTime = new Date(entry.publishTime);
    return publishTime <= now && entry.status === 'scheduled';
  });
  
  for (const entry of readyToPublish) {
    try {
      await this.publishContent(entry.productionId);
      this.logger.info(`Auto-published: ${entry.title}`);
    } catch (error) {
      this.logger.error(`Failed to auto-publish ${entry.title}:`, error);
      // Mark as failed but don't stop processing
      entry.status = 'failed';
      entry.error = error.message;
      await this.db.updateScheduleEntry(entry);
    }
  }
  
  return readyToPublish.length;
}

Priority System

publishing-scheduling-agent.js
calculatePriority(strategy) {
  let priority = 50; // Base priority
  
  // Adjust based on estimated views
  if (strategy.estimatedViews > 100000) priority += 30;
  else if (strategy.estimatedViews > 50000) priority += 20;
  else if (strategy.estimatedViews > 10000) priority += 10;
  
  // Adjust based on trend score
  if (strategy.competitorAnalysis && strategy.competitorAnalysis.length > 0) {
    priority += 10;
  }
  
  // Time sensitivity
  const hoursUntilPublish = (new Date(strategy.bestPublishTime) - new Date()) / (1000 * 60 * 60);
  if (hoursUntilPublish < 24) priority += 20;
  else if (hoursUntilPublish < 48) priority += 10;
  
  return Math.min(100, priority);
}
Time-sensitive trending topics with high estimated views
Standard content with moderate estimated engagement
Evergreen content that can be published anytime

Upload Components

uploadThumbnail()

Uploads custom thumbnail after video processing:
publishing-scheduling-agent.js
async uploadThumbnail(videoId, thumbnailPath) {
  try {
    const thumbnailBuffer = await fs.readFile(thumbnailPath);
    
    await this.youtube.thumbnails.set({
      videoId: videoId,
      media: {
        body: thumbnailBuffer
      }
    });
    
    this.logger.info(`Thumbnail uploaded for video: ${videoId}`);
  } catch (error) {
    this.logger.error(`Failed to upload thumbnail: ${error.message}`);
  }
}

uploadCaptions()

Adds caption files for accessibility:
publishing-scheduling-agent.js
async uploadCaptions(videoId, captionsPath) {
  try {
    const captionsContent = await fs.readFile(captionsPath, 'utf8');
    
    await this.youtube.captions.insert({
      part: 'snippet',
      requestBody: {
        snippet: {
          videoId: videoId,
          language: 'en',
          name: 'English Captions',
          isDraft: false
        }
      },
      media: {
        body: captionsContent
      }
    });
    
    this.logger.info(`Captions uploaded for video: ${videoId}`);
  } catch (error) {
    this.logger.error(`Failed to upload captions: ${error.message}`);
  }
}

Emergency Publishing

For urgent content needs:

emergencyPublish()

publishing-scheduling-agent.js
async emergencyPublish(contentId, delayMinutes = 0) {
  this.logger.info(`Emergency publish requested: ${contentId}`);
  
  const entry = this.publishQueue.find(e => 
    e.productionId === contentId || e.id === contentId
  );
  
  if (!entry) {
    throw new Error(`Content not found: ${contentId}`);
  }
  
  if (delayMinutes > 0) {
    const newPublishTime = new Date(Date.now() + (delayMinutes * 60 * 1000));
    entry.publishTime = newPublishTime.toISOString();
    await this.db.updateScheduleEntry(entry);
    this.logger.info(`Emergency scheduled for: ${entry.publishTime}`);
    return entry;
  } else {
    return await this.publishContent(contentId);
  }
}

Pause & Resume

publishing-scheduling-agent.js
async pauseScheduledContent(contentId) {
  const entry = this.publishQueue.find(e => 
    e.productionId === contentId || e.id === contentId
  );
  
  if (!entry) throw new Error(`Content not found: ${contentId}`);
  
  entry.status = 'paused';
  await this.db.updateScheduleEntry(entry);
  
  this.logger.info(`Content paused: ${entry.title}`);
  return entry;
}

async resumeScheduledContent(contentId, newPublishTime = null) {
  const entry = this.publishQueue.find(e => 
    e.productionId === contentId || e.id === contentId
  );
  
  if (!entry) throw new Error(`Content not found: ${contentId}`);
  
  entry.status = 'scheduled';
  if (newPublishTime) {
    entry.publishTime = new Date(newPublishTime).toISOString();
  }
  
  await this.db.updateScheduleEntry(entry);
  
  this.logger.info(`Content resumed: ${entry.title}`);
  return entry;
}

Publishing Reports

The agent generates comprehensive publishing analytics:

createPublishingReport()

publishing-scheduling-agent.js
async createPublishingReport() {
  const report = {
    queueStatus: {
      total: this.publishQueue.length,
      scheduled: this.publishQueue.filter(e => e.status === 'scheduled').length,
      published: this.publishQueue.filter(e => e.status === 'published').length,
      failed: this.publishQueue.filter(e => e.status === 'failed').length
    },
    upcomingPublications: await this.getUpcomingSchedule(7),
    recentPublications: this.publishQueue
      .filter(e => e.status === 'published' && 
              new Date(e.publishedAt) > new Date(Date.now() - 7 * 24 * 60 * 60 * 1000))
      .sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt)),
    performance: await this.getPublishingPerformance(),
    generatedAt: new Date().toISOString()
  };
  
  return report;
}

Publishing Performance Metrics

publishing-scheduling-agent.js
async getPublishingPerformance() {
  const published = this.publishQueue.filter(e => e.status === 'published');
  
  if (published.length === 0) {
    return {
      totalPublished: 0,
      averageScheduleAccuracy: 0,
      publishingFrequency: 0
    };
  }
  
  // Calculate schedule accuracy
  let totalDelay = 0;
  let accuratePublishes = 0;
  
  published.forEach(entry => {
    const scheduledTime = new Date(entry.publishTime);
    const actualTime = new Date(entry.publishedAt);
    const delay = Math.abs(actualTime - scheduledTime) / (1000 * 60);
    
    totalDelay += delay;
    if (delay <= 5) accuratePublishes++;
  });
  
  const averageDelay = totalDelay / published.length;
  const accuracyRate = (accuratePublishes / published.length) * 100;
  
  return {
    totalPublished: published.length,
    averageScheduleAccuracy: `${accuracyRate.toFixed(1)}%`,
    averageDelay: `${averageDelay.toFixed(1)} minutes`,
    publishingFrequency: this.calculatePublishingFrequency(published)
  };
}

Configuration

.env
# Default privacy status (public|unlisted|private)
DEFAULT_PRIVACY_STATUS=public

# Auto-publish enabled
AUTO_PUBLISH_ENABLED=true

# Queue processing interval (minutes)
QUEUE_CHECK_INTERVAL=5

# Maximum retries for failed uploads
MAX_UPLOAD_RETRIES=3

# Upload timeout (minutes)
UPLOAD_TIMEOUT=30

Example Schedule Entry

{
  "productionId": "prod_1234567890_abc123",
  "title": "Ultimate Guide to AI Technology Trends (2026)",
  "publishTime": "2026-03-07T14:00:00.000Z",
  "status": "scheduled",
  "priority": 85,
  "metadata": {
    "seo": { /* SEO data */ },
    "thumbnail": { /* Thumbnail data */ },
    "video": { /* Video file data */ },
    "captions": { /* Caption file data */ }
  },
  "youtubeId": "dQw4w9WgXcQ",
  "youtubeUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "publishedAt": "2026-03-07T14:00:23.000Z"
}

Best Practices

Queue content at least 24 hours before intended publish time for optimal scheduling
Regularly check the publishing queue to identify any failed uploads
Use analytics data to continuously refine optimal publishing times for your specific audience
Publish on a consistent schedule to build audience expectations

Performance Metrics

Upload Time

5-15 minutes

Schedule Accuracy

±5 minutes

Success Rate

98%+

Next Steps

Analytics Agent

Track performance after publishing

Production Agent

Learn about the video production pipeline

Configuration

Configure publishing settings

API Reference

View complete API documentation

Build docs developers (and LLMs) love