EmDash's plugin architecture enables a content syndication pipeline that automatically distributes blog posts to LinkedIn Articles, Dev.to, Medium, Hacker News, and email newsletters — all from a single publish action inside EmDash. ## The Syndication Challenge Content teams publish on the EmDash site and then face a tedious manual workflow: copy the post into LinkedIn's editor, reformat for Dev.to's markdown, adjust for Medium's import tool, craft a Hacker News submission, and write an email blast. Each platform requires different formatting, character limits, and metadata. The result is that most content gets published on the main site and never reaches the audiences on other platforms. Manual syndication is time-consuming, error-prone, and rarely happens consistently.

Common bottlenecks include:

- **Format fragmentation** — HTML on your site, Markdown on Dev.to, rich text on LinkedIn, plain text for HN.

- **Timing drift** — ideally syndication happens within hours of initial publish, but manual workflows slip by days or weeks.

- **Metadata mismatch** — canonical URLs, tags, and excerpts need to be correct on each platform for SEO.

- **No tracking** — without centralized analytics, you cannot measure which syndication channel drives the most traffic. ## Architecture: The Syndication Pipeline

EmDash's plugin hooks allow you to register a post-publish callback that triggers the syndication pipeline automatically. Here is the architecture:

| Component | Purpose | Technology |

|-----------|---------|-----------|

| Publish Hook | Trigger on post status change | EmDash plugin middleware |

| Format Renderer | Convert to platform-specific formats | Template engine + markdown-it |

| Channel Adapter | Platform-specific API client | Fetch API + OAuth tokens |

| Queue Manager | Retry failed syndications | D1 queue table |

| Analytics Tracker | Log syndication events | D1 events table |

The pipeline runs entirely on Cloudflare Workers via EmDash's plugin system. No external cron jobs or queue infrastructure needed. ## Step 1: The Publish Hook

The first piece is a plugin middleware that fires whenever a post's status changes to 'published':

```javascript

// emdash-plugin-syndication/hooks.js

export default {

async afterPostSave(post, context) {

if (post.status === 'published' && post.wasDraft) {

await context.env.SYNDICATION_QUEUE.put(

`syndicate:${post.slug}`,

JSON.stringify({

slug: post.slug,

title: post.title,

body: post.body,

excerpt: post.excerpt,

tags: post.tags,

publishedAt: post.published_at,

channels: ['devto', 'linkedin', 'medium', 'hn']

}),

{ expirationTtl: 86400 }

);

}

return post;

}

};

```

This hook enqueues a syndication job to Cloudflare Workers Queues (or KV with a TTL for simpler deployments). A separate worker picks up the job and processes each channel sequentially. ## Step 2: Format Conversion

Each platform expects a different content format. The plugin maintains a format adapter for each channel:

| Channel | Format | Body Limit | Image Handling |

|---------|--------|-----------|----------------|

| Dev.to | Markdown | None | Upload to DEV CDN |

| LinkedIn | Rich Text | 3000 chars | Use OG image URL |

| Medium | Markdown (import) | None | Import via URL |

| Hacker News | Plain text | 2000 chars | Append story URL |

| Email Digest | HTML | None | Inline images |

The format renderer converts EmDash's internal HTML body to each target format using a combination of regex transforms and markdown-it conversion:

```javascript

const adapters = {

devto: (post) => ({

body_markdown: htmlToMarkdown(post.body),

tags: post.tags.slice(0, 4),

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

published: true

}),

linkedin: (post) => ({

commentator: 'urn:li:person:ai-kit',

content: {

article: {

title: post.title,

description: post.excerpt,

source: 'https://ai-kit.net',

thumbnailUrl: `https://ai-kit.net/og/${post.slug}.png`,

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

}

},

distribution: {

feedDistribution: 'MAIN_FEED',

targetEntities: []

}

}),

medium: (post) => ({

title: post.title,

contentFormat: 'markdown',

content: htmlToMarkdown(post.body),

tags: post.tags.slice(0, 3),

publishStatus: 'public',

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

})

};

```

Each adapter handles platform-specific requirements such as tag limits, character constraints, and canonical URL injection for SEO. ## Step 3: Channel Authentication

OAuth tokens for each platform are stored in Cloudflare Workers Secrets and accessed via context.env. The plugin uses a token refresh pattern to handle expiring credentials:

```javascript

async function getValidToken(channel, context) {

const token = await context.env.SECRETS.get(`${channel}_token`);

const expiresAt = await context.env.SECRETS.get(`${channel}_expires_at`);

if (token && expiresAt && Date.now() < parseInt(expiresAt)) {

return token;

}

// Refresh token

const refreshToken = await context.env.SECRETS.get(`${channel}_refresh_token`);

const response = await refreshAccessToken(channel, refreshToken);

await context.env.SECRETS.put(`${channel}_token`, response.access_token);

await context.env.SECRETS.put(

`${channel}_expires_at`,

String(Date.now() + response.expires_in * 1000)

);

return response.access_token;

}

```

This ensures the syndication pipeline never fails due to expired credentials. Failed tokens trigger a notification to the admin via Telegram or email. ## Step 4: Analytics and Error Tracking

Each syndication attempt is logged to a D1 events table:

```sql

CREATE TABLE syndication_events (

id INTEGER PRIMARY KEY AUTOINCREMENT,

post_slug TEXT NOT NULL,

channel TEXT NOT NULL,

status TEXT NOT NULL, -- 'success', 'failed', 'retrying'

url TEXT,

error TEXT,

attempted_at INTEGER NOT NULL,

retry_count INTEGER DEFAULT 0

);

CREATE INDEX idx_syndication_post ON syndication_events(post_slug);

```

The pipeline retries failed channels up to 3 times with exponential backoff (30s, 2min, 10min). After exhausting retries, the event is marked as permanently failed and a notification is dispatched. ## Practical Implementation Tips

- **Stagger syndication timing** — do not publish to all channels simultaneously. Send to Dev.to and Medium first (they index quickly), then LinkedIn (slower to appear in feeds), then email digests (avoid spamming subscribers who also follow you on other platforms).

- **Set canonical URLs correctly** on every syndicated copy. Most platforms support a canonical_url field in their API — use it to prevent SEO dilution.

- **Rate-limit per channel** — Dev.to allows 5 API calls per minute, LinkedIn has daily post limits. The plugin maintains a rate limiter per channel using D1 counters.

- **Preview before posting** — the plugin includes a dry-run mode that returns the formatted payload without actually posting. This lets you preview syndicated content in EmDash's admin UI before it goes live.

- **Handle platform-specific formatting** — LinkedIn strips code blocks, Medium expects Gist embeds, Dev.to loves code fences. The format renderer customizes the output for each platform's quirks. ## Measuring Success

Track these metrics to evaluate your syndication pipeline:

- **Syndication velocity** — time from initial publish to full syndication across all channels (target: under 10 minutes).

- **Channel performance** — which syndication channel drives the most referrer traffic back to ai-kit.net.

- **Failure rate** — percentage of syndication attempts that require retries. High failure rates indicate token issues or API changes.

- **SEO impact** — monitor whether syndicated copies outrank your canonical page for target keywords. If they do, strengthen canonical tags or delay syndication by 24 hours.

- **Time saved** — estimate the hours of manual copy-paste eliminated per week. A team publishing 3 posts per week typically saves 2-3 hours per week with automated syndication.

Building a content syndication plugin for EmDash turns a one-platform CMS into a multi-channel distribution engine. Each post you publish on ai-kit.net automatically reaches Dev.to's developer audience, LinkedIn's professional network, Medium's general readership, Hacker News's tech community, and your email subscribers — with zero manual effort after the initial publish click.