Skip to main content

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

  1. Use batch operations - Schedule multiple posts in one API call
  2. Validate before scheduling - Use /posts/preflight to check content
  3. Handle timezones carefully - Store times in UTC, convert for display
  4. Monitor delivery - Set up webhooks to track post status
  5. Plan buffer time - Don't schedule too close to "now"