Scheduling Workflows
This guide covers common scheduling patterns for creators and operators using the Flow API.
Overview
Flow's scheduling system allows you to:
- Schedule posts for specific times
- Create recurring content schedules
- Batch schedule content for a week or month
- Optimize posting times across timezones
Scheduling Basics
Schedule a Single Post
import { Flow } from '@flowdev/sdk';
const flow = new Flow('flow_sk_live_...');
// Schedule for a specific time
const post = await flow.posts.create({
channelId: 'channel_123',
content: 'Scheduled post!',
scheduledFor: new Date('2024-12-25T10:00:00Z'),
});
Schedule for "Now" (Immediate)
// Omit scheduledFor for immediate posting
const post = await flow.posts.create({
channelId: 'channel_123',
content: 'Post immediately!',
});
Batch Scheduling
Schedule a Week of Content
async function scheduleWeekOfContent(channelId: string, contents: string[]) {
const posts = contents.map((content, index) => {
const date = new Date();
date.setDate(date.getDate() + index + 1); // Start tomorrow
date.setHours(9, 0, 0, 0); // 9 AM local
return {
channelId,
content,
scheduledFor: date,
};
});
const result = await flow.posts.createBatch({ posts });
console.log(`Created ${result.created.length} posts`);
if (result.failed.length > 0) {
console.error(`Failed: ${result.failed.length} posts`);
}
return result;
}
// Usage
await scheduleWeekOfContent('channel_123', [
'Monday motivation! Start strong.',
'Tuesday tip: Plan your content ahead.',
'Wednesday wisdom: Quality over quantity.',
'Thursday thoughts: Engage with your audience.',
'Friday feature: Showcase your work.',
'Weekend vibes: Take time to reflect.',
'Sunday setup: Prepare for the week ahead.',
]);
Schedule at Optimal Times
// Define optimal posting times per day
const optimalTimes = {
monday: ['09:00', '12:00', '17:00'],
tuesday: ['08:00', '13:00', '18:00'],
wednesday: ['09:00', '12:00', '17:00'],
thursday: ['08:00', '13:00', '18:00'],
friday: ['09:00', '12:00', '16:00'],
saturday: ['10:00', '14:00'],
sunday: ['11:00', '15:00'],
};
function getNextOptimalSlot(fromDate: Date): Date {
const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
for (let dayOffset = 0; dayOffset < 7; dayOffset++) {
const checkDate = new Date(fromDate);
checkDate.setDate(checkDate.getDate() + dayOffset);
const dayName = days[checkDate.getDay()];
const times = optimalTimes[dayName] || [];
for (const time of times) {
const [hours, minutes] = time.split(':').map(Number);
const slotDate = new Date(checkDate);
slotDate.setHours(hours, minutes, 0, 0);
if (slotDate > fromDate) {
return slotDate;
}
}
}
// Fallback: tomorrow at 9 AM
const fallback = new Date(fromDate);
fallback.setDate(fallback.getDate() + 1);
fallback.setHours(9, 0, 0, 0);
return fallback;
}
Content Cadence Patterns
Daily Posting
async function scheduleDailyPosts(channelId: string, contents: string[], startDate: Date) {
const posts = contents.map((content, index) => {
const date = new Date(startDate);
date.setDate(date.getDate() + index);
return { channelId, content, scheduledFor: date };
});
return await flow.posts.createBatch({ posts });
}
Weekly Themes
const weeklyThemes = {
monday: { prefix: '🎯 Goal Monday:', hashtags: '#MondayMotivation #Goals' },
tuesday: { prefix: '💡 Tip Tuesday:', hashtags: '#TipTuesday #ProTips' },
wednesday: { prefix: '📚 Learn Wednesday:', hashtags: '#WednesdayWisdom' },
thursday: { prefix: '🔥 Throwback Thursday:', hashtags: '#TBT #Throwback' },
friday: { prefix: '🎉 Feature Friday:', hashtags: '#FeatureFriday' },
};
function formatPostForDay(content: string, date: Date): string {
const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
const dayName = days[date.getDay()];
const theme = weeklyThemes[dayName];
if (theme) {
return `${theme.prefix} ${content}\n\n${theme.hashtags}`;
}
return content;
}
Timezone Handling
Schedule in User's Timezone
function scheduleInTimezone(
content: string,
localTime: string, // e.g., "09:00"
timezone: string, // e.g., "America/New_York"
channelId: string
) {
// Parse local time
const [hours, minutes] = localTime.split(':').map(Number);
// Create date in user's timezone
const date = new Date();
date.setHours(hours, minutes, 0, 0);
// Convert to UTC using Intl
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
const localDate = new Date(date.toLocaleString('en-US', { timeZone: timezone }));
const offset = localDate.getTime() - utcDate.getTime();
const scheduledFor = new Date(date.getTime() - offset);
return flow.posts.create({
channelId,
content,
scheduledFor,
});
}
Multi-Timezone Campaign
async function scheduleGlobalCampaign(
content: string,
channelId: string,
timezones: string[]
) {
const posts = timezones.map(tz => {
// Schedule for 9 AM in each timezone
const date = new Date();
date.setDate(date.getDate() + 1); // Tomorrow
return {
channelId,
content: `${content}\n\n[${tz}]`,
scheduledFor: scheduleInTimezone(content, '09:00', tz, channelId),
};
});
return await flow.posts.createBatch({ posts });
}
// Usage
await scheduleGlobalCampaign(
'Big announcement!',
'channel_123',
['America/New_York', 'Europe/London', 'Asia/Tokyo']
);
Managing Scheduled Content
View Upcoming Posts
async function getUpcomingPosts(channelId: string, days: number = 7) {
const now = Date.now();
const futureDate = now + days * 24 * 60 * 60 * 1000;
const posts = await flow.posts.list({
filter: {
channelId,
status: 'queued',
scheduledFor: { gte: now, lte: futureDate },
},
sort: 'scheduledFor',
per_page: 100,
});
return posts.data;
}
Reschedule Posts
async function reschedulePost(postId: string, newTime: Date) {
return await flow.posts.update(postId, {
scheduledFor: newTime,
});
}
async function rescheduleAllByHours(channelId: string, hoursOffset: number) {
const posts = await flow.posts.list({
filter: { channelId, status: 'queued' },
});
const updates = posts.data.map(post => ({
id: post.id,
scheduledFor: new Date(post.scheduledFor + hoursOffset * 60 * 60 * 1000),
}));
return await flow.posts.updateBatch({ posts: updates });
}
Cancel Scheduled Posts
async function cancelUpcomingPosts(channelId: string) {
const posts = await flow.posts.list({
filter: { channelId, status: 'queued' },
});
if (posts.data.length === 0) return;
return await flow.posts.deleteBatch({
postIds: posts.data.map(p => p.id),
});
}
Best Practices
- Use batch operations - Schedule multiple posts in one API call
- Validate before scheduling - Use
/posts/preflightto check content - Handle timezones carefully - Store times in UTC, convert for display
- Monitor delivery - Set up webhooks to track post status
- Plan buffer time - Don't schedule too close to "now"