Building an Automated Publishing Pipeline: From Craft to Ghost

For months, I’ve been publishing my weekly newsletter, The Ephemeral Scrapbook, using a manual process: write in Craft, export to Ulysses, copy to Ghost, reformat everything, add images, fix formatting issues, and finally publish. It worked, but it was tedious and time-consuming.

Today, that process is fully automated. Here’s how Claude and I built it together.

The Challenge

My workflow had become a bottleneck:

  • Writing newsletters in Craft Docs (my preferred writing environment)
  • Exporting to Ulysses as an intermediary step
  • Manual copy/paste to Ghost (my publishing platform)
  • Reformatting all the markdown and HTML
  • Dealing with Craft-specific formatting that Ghost didn’t understand
  • Adding metadata like excerpts and tags manually

I wanted automation, but I also wanted to understand the infrastructure I was building. That’s where working with Claude became invaluable—not just executing commands, but learning and iterating together.

The Solution: n8n Workflow Automation

We decided to build an n8n workflow that would:

  1. Search for a document in Craft by title
  2. Fetch all the content blocks
  3. Transform Craft’s markdown/blocks into clean HTML
  4. Publish to Ghost as a draft
  5. Return confirmation with the post URL

Simple in concept, complex in execution.

The Journey: Key Milestones

Milestone 1: Understanding the Architecture

Challenge: Should we use multiple workflows or one unified workflow?

Decision: One end-to-end workflow that handles everything from search to publish.

Learning: Simplicity wins. Rather than orchestrating multiple workflows, we built one cohesive pipeline that’s easier to debug and maintain.

Workflow nodes:

  • Webhook (trigger)
  • HTTP Request (search Craft)
  • HTTP Request (fetch document)
  • Code (transform to HTML)
  • HTTP Request (publish to Ghost)
  • Respond to Webhook

The Iterative Building Process

One of the most important decisions we made was to build and test incrementally. Rather than assembling the entire workflow at once and hoping it would work, we added one node at a time, testing after each addition.

The Testing Cadence:

  1. Add Webhook → Test: Confirmed the webhook received the query parameter correctly
  2. Add Search Node → Test: Verified we could find the document and get the correct document ID
  3. Add Fetch Node → Test: Checked that we retrieved all 54 blocks of content with the proper nested structure
  4. Add Code Node → Test: Validated the HTML transformation, checking for clean output without Craft tags
  5. Add Ghost Publish Node → Test: Ensured the post was created as a draft with all content intact
  6. Add Response Node → Test: Confirmed the workflow returned post details back to Claude

Why This Mattered:

Each test revealed issues that would have been much harder to debug in a complete workflow:

  • The search node helped us understand Craft returns multiple matches (we needed the first result)
  • The fetch node showed us the nested structure (parent document → edition page → content blocks)
  • The code node iterations caught formatting issues (<callout> tags, ## symbols, <highlight> tags)
  • The Ghost publish node revealed we needed the ?source=html query parameter

By testing at each step, we could pinpoint exactly where problems occurred. When something didn’t work, we knew it was the node we just added, not some mysterious interaction between distant parts of the workflow.

This incremental approach turned what could have been hours of debugging into a smooth building process. Each successful test gave us confidence to move forward, and each failure was easy to isolate and fix.

Milestone 2: Building the HTML Transformer

Challenge: Craft uses its own markdown dialect with special tags like <callout>, <highlight color="blue">, and markdown headers in text blocks.

What we built: A comprehensive JavaScript transformation engine that:

  • Removes Craft-specific tags (<callout>, <highlight>)
  • Converts markdown formatting (bold, italic, links, code)
  • Processes different block types (text, headers, quotes, code, images, videos)
  • Handles rich URL blocks (YouTube embeds)
  • Preserves anchor links for internal navigation
  • Generates proper HTML for Ghost’s Lexical editor

Key functions:

  • markdownToHtml() - Converts inline markdown to HTML
  • processBlock() - Handles each block type (text, image, richUrl, code, line, etc.)

Milestone 3: Testing and Validation

The Process:

  • Test with real content (Edition 2025-52 with 54 blocks)
  • Verify HTML output in Ghost’s editor
  • Check for Craft formatting artifacts
  • Confirm all sections, videos, quotes, and images are preserved

Quality Checks:

  • ✅ No <callout> tags
  • ✅ No <highlight> tags
  • ✅ No ## symbols in headers
  • ✅ All YouTube videos embedded correctly
  • ✅ Blockquotes formatted properly
  • ✅ Images included
  • ✅ 9-minute reading time (17,000+ characters)

The Final Workflow

Input: {"query": "The Ephemeral Scrapbook — Edition 2025-52"}

Output: Draft post in Ghost with:

  • Complete HTML content
  • All formatting preserved
  • Clean structure
  • Ready for manual review (add images, tags, excerpt)

Execution time: ~3-4 seconds total

  • Search: 1-2 seconds
  • Fetch: 1-2 seconds
  • Transform: 36-84ms
  • Publish: 600-900ms

The Tools

  • Craft: My writing environment with a powerful API
  • Ghost: My publishing platform with a robust Admin API
  • n8n: Workflow automation platform (self-hosted on DigitalOcean)
  • Claude AI: My pair-programming partner via MCP (Model Context Protocol)

The Result

The workflow is production-ready. My publishing workflow went from 20+ minutes of manual work through Craft, Ulysses, and Ghost to a single command:

“Claude, publish Edition 2026-01 to Ghost”

And it just works. 🎉