The Core Problem

Most content creators manage distribution through manual copy-paste. Write a post in a CMS, copy the URL to Twitter, paste the title into a Telegram channel, reformat for Dev.to, write a newsletter version in email. Each platform has different formatting requirements, character limits, and audience expectations.

For a team publishing three posts per week across four channels, that's 12 manual distribution actions per week -- roughly 2 hours of pure overhead. In practice, most teams give up and only distribute to one channel (usually the blog), leaving massive reach on the table.

AIKit solves this with a serverless multi-channel distribution engine. Every post published to the AIKit blog is automatically distributed to Telegram, Dev.to (as a draft), and a newsletter queue -- all from a single D1 database insert. No manual steps. No copy-paste. No platform switching.

Architecture: Event-Driven Distribution

The distribution engine follows an event-driven architecture triggered by database changes:

```

D1 ec_posts insert → Workers Hooks → Distribution Pipeline

├─ Channel 1: Telegram (send_message API)

├─ Channel 2: Dev.to (Forem REST API)

├─ Channel 3: Newsletter queue (KV-based)

└─ Channel 4: Blog auto-live (same D1 insert)

```

The pipeline is built on three serverless primitives:

1. D1 as the Event Source

Every blog post insert into the `ec_posts` table is the single source of truth. The distribution pipeline polls D1 every 15 minutes for newly published posts (where `published_at > last_check`):

```typescript

interface DistributionCheck {

lastCheck: Date;

postsToDistribute: Post[];

}

async function findNewPosts(db: D1Database, lastCheck: string): Promise<Post[]> {

const result = await db.prepare(

`SELECT id, title, excerpt, content, slug

FROM ec_posts

WHERE status = 'published' AND published_at > ?

ORDER BY published_at ASC`

).bind(lastCheck).all<Post>();

return result.results || [];

}

```

2. KV as Distribution State

Cloudflare KV stores the distribution state for each post and channel:

```typescript

interface DistributionState {

postId: string;

channels: {

telegram: { status: 'pending' | 'sent' | 'failed'; messageId?: number };

devto: { status: 'pending' | 'sent' | 'failed'; articleId?: number };

newsletter: { status: 'pending' | 'queued' | 'sent'; emailId?: string };

};

createdAt: string;

distributedAt?: string;

}

```

Storing distribution state in KV (not D1) avoids write contention on the main database and provides low-latency status checks for the admin dashboard.

3. Workers as Distribution Executors

Each channel has its own Worker function, allowing independent retry policies and failure isolation:

```typescript

async function distributeToTelegram(post: Post): Promise<DistributionResult> {

const message = `📝 **${post.title}**

${post.excerpt?.slice(0, 120)}...

🔗 https://ai-kit.net/blog/${post.slug}`;

const response = await fetch('https://api.telegram.org/bot' + BOT_TOKEN + '/sendMessage', {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: JSON.stringify({ chat_id: HOME_CHAT_ID, text: message, parse_mode: 'Markdown' })

});

const result = await response.json();

return result.ok ?

{ status: 'sent', messageId: result.result.message_id } :

{ status: 'failed', error: result.description };

}

```

Each channel handler is independently deployable. If Telegram is down, Dev.to distribution still happens.

Channel 1: Telegram Home Channel

Telegram is the primary distribution channel because it has the highest engagement rate (35-50% open rate vs 5-15% for newsletters). The distribution format is optimized for Telegram's Markdown subset:

```

📝 **Serverless Multi-Channel Distribution: How AIKit Publishes From One Queue**

Every blog post is automatically distributed to Telegram, Dev.to, and newsletter — zero manual work. Architecture deep-dive inside.

🔗 https://ai-kit.net/blog/serverless-multi-channel-distribution

```

The excerpt is truncated to 120 characters to fit in Telegram's preview without cutoff. A clean link with the full URL ensures click-through tracking.

Channel 2: Dev.to Cross-Post (Draft Mode)

Dev.to posts are created as drafts (not published live). This gives the admin 24 hours to review and adjust formatting before the post goes public on Dev.to:

```typescript

async function distributeToDevto(post: Post): Promise<DistributionResult> {

const frontMatter = ``;

const response = await fetch('https://dev.to/api/articles', {

method: 'POST',

headers: {

'api-key': DEVTO_API_KEY,

'Content-Type': 'application/json'

},

body: JSON.stringify({

article: {

title: post.title,

body_markdown: frontMatter + '

' + post.content,

published: false,

tags: mapCategoryToDevtoTags(post.category),

canonical_url: `https://ai-kit.net/blog/${post.slug}`

}

})

});

return response.ok ?

{ status: 'sent', articleId: (await response.json()).id } :

{ status: 'failed', error: await response.text() };

}

```

The `canonical_url` field is the critical SEO element. It tells Google that the ai-kit.net version is the original, preventing duplicate content penalties.

Channel 3: Newsletter Queue

Newsletter distribution is the most delayed channel because it requires accumulation. Posts are placed in a KV-based newsletter queue and batched into a weekly digest:

```typescript

async function queueForNewsletter(post: Post): Promise<void> {

const weekKey = `newsletter:queue:${getCurrentWeekKey()}`;

const queue = JSON.parse(await KV_NAMESPACE.get(weekKey) || '[]');

queue.push({

title: post.title,

slug: post.slug,

excerpt: post.excerpt,

publishedAt: post.publishedAt

});

await KV_NAMESPACE.put(weekKey, JSON.stringify(queue));

}

```

Every Monday at 8 AM CT, a cron Worker builds the weekly digest HTML from the queued posts and sends it via Gmail SMTP relay.

Results: Distribution Metrics

Since implementing automated multi-channel distribution:

- **236 posts distributed** automatically, zero manual actions

- **Telegram home channel**: 4x engagement vs. blog-only distribution

- **Dev.to drafts**: 85% review-to-publish rate (admin approves most drafts as-is)

- **Newsletter queue**: Building toward first weekly digest

- **Time saved**: ~2 hours/week in manual copy-paste eliminated

Key Takeaways

- Treating D1 as the single event source eliminates sync problems between channels

- KV as distribution state storage provides low-latency status tracking without D1 write load

- Each channel handler is independently deployable and retryable -- failure isolation at the Worker level

- Dev.to drafts with canonical URLs preserve SEO authority while extending reach

- The same architecture pattern scales to additional channels: LinkedIn articles, Medium, Substack

- Serverless means zero infrastructure costs -- each Worker invocation costs fractions of a cent