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
Content Enters Queue
Production agent adds completed content to publish queue
Time Optimization
Agent analyzes optimal publish time based on channel analytics
Priority Assignment
Content is prioritized based on trend sensitivity and estimated impact
Automatic Publishing
At scheduled time, agent uploads video with all metadata
Thumbnail Upload
Custom thumbnail is uploaded after video processing
Caption Upload
Generated captions are added for accessibility
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:
Best Days
Best Hours
Worst Times
Tuesday - Thursday : Peak engagement daysSaturday - Sunday : Strong weekend trafficoptimalDays : [ 'Tuesday' , 'Wednesday' , 'Thursday' ]
2-4 PM : Afternoon peak (14:00-16:00)8-10 PM : Evening peak (20:00-22:00)optimalHours : [ 14 , 15 , 16 , 20 , 21 ]
Monday mornings : Low engagementLate night/early morning : Minimal trafficworstHours : [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]
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-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
# 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
Schedule Accuracy ±5 minutes
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