From MDX Files to a Real Publishing Workflow

How I replaced a brittle Next.js blog pipeline with ERPNext, n8n, and a couple of webhooks.

 · 4 min read

Writing a blog post on this site up to now has meant opening a code editor. Not a text editor — a code editor. Every post lived as a .mdx file in the repo, which meant every post was also a commit. Front matter, slug, category, excerpt — all of it hand-typed in a format that has nothing to do with writing and everything to do with making a parser happy. It worked, but it had the kind of friction that kills any motivation to actually sit down and write. I didn't want to solve that by paying for a hosted blog platform and its "premium" feature tier just to get basic quality-of-life stuff.

So, what do I do? I build my own ecosystem.

The old problem

The MDX approach is common in Next.js blogs and it has real appeal: your content lives in the repo, versioned alongside your code, easily portable. But for a personal site where I'm the only author and I care much more about writing than about git history for blog posts, those benefits don't really land. What I actually experienced was:

Write post → format as MDX → commit → push → wait for build → check the site → notice a typo → repeat. The feedback loop was long, the authoring experience was spartan, and there was no automated way to notify anyone that a new post existed. Subscribers had to just happen to check the site. For that matter, there wasn't a way to even collect subscribers.

The new stack

Blog content now lives in ERPNext, which has a built-in blogging module with a rich text editor that actually behaves like a writing tool. I write the post there, hit publish, and everything else happens automatically.

Publish flow: ERPNext publish → webhook → n8n workflow → Newsletter record + GitHub Actions dispatch

When a post is published, ERPNext fires a webhook. n8n catches it, creates a Newsletter record in ERPNext pointing at both the "Blog Subscribers" and "Website" email groups, and triggers the send through ERPNext's native email infrastructure. At the same time, n8n hits the GitHub Actions workflow dispatch API to kick off a site rebuild so the homepage reflects the new post. The whole thing is automatic — no manual steps after I hit publish.

ERPNext handles deduplication natively when a Newsletter references multiple email groups, so subscribers who are on both lists get exactly one email. Retry logic is baked into the n8n workflow: one automatic retry on failure before it fires off an admin notification through ERPNext's outbound email.

The subscribe form

There's also a new subscriber form on the blog listing page, rendered in Next.js outside the ERPNext iframe so I have full control over the styling. It presents two options with plain-language descriptions: blog post notifications only, or everything including future updates from the site. Choosing the first adds you to "Blog Subscribers." Choosing the second adds you to "Website." You can be on both. n8n routes the submission to the right group based on the selection.

What's in an iframe?

ERPNext's blog module renders inside an iframe on the Next.js site. The existing setup for the main blog listing already used a ?embed=1 parameter to suppress the ERPNext chrome, but individual post pages weren't getting the same treatment — meaning if you scrolled far enough, the ERPNext header or footer could bleed into view. That's fixed now. Both the listing and the post pages constrain the iframe so no ERPNext UI is ever visible to a reader.

Why ERPNext for a blog?

Fair question. ERPNext is an Enterprise Resource Planning (ERP) program. It's overkill as a dedicated blogging platform. But it's already running in my stack for other reasons, it has a solid blog module, and using it means I'm not adding another service just for content management. The rich text editor is genuinely pleasant to write in compared to MDX. ERPNext also handles the Newsletter and email group infrastructure natively, so the subscriber workflow just works without bolting on a third-party email service. It also has a built-in comments function, which means I could pull Remark42 out of my home lab entirely. Now everything blog-related lives in one interface. The only thing I lose out on is Google Analytics off the main site, but I'll find a fix for that eventually.

A side note: I work in AI, and I think about data ownership more than I used to. I'm not against the tools. I use them every day. I'm quite proficient at this point. I have my own OpenClaw bot running with Ollama open source models. I use Claude Code and Gemini to help with application building. I play around with Suno for musical inspiration (I once studied classical guitar performance at The Boston Conservatory). I run ComfyUI with self-hosted models for GenAI image and video. But there's something clarifying about watching this industry move fast that makes you want to be intentional about what you put where, and who has access to it. Running your own stack is one way to do that. Your mailing list is yours. Your posts aren't training someone else's model by default. That's a choice I'm comfortable with.

The workflow is simpler now. Write something, hit publish, walk away. That's how it should have been from the start.

If you want a how-to on anything I mentioned here, let me know in the comments below!


No comments yet.

Add a comment
Ctrl+Enter to add comment