I help run growth marketing for B2B companies, and one job kept eating my week: publishing the same idea, rewritten, across a handful of publishing platforms. So I built a small Node CLI to do it for me. Here is how it works and what I learned.
This is not a "growth hack" post. It is a practical build log. If you publish content in more than one place, you might find the pattern useful.
The problem
Posting an article by hand is slow. You log in, paste, fix formatting, add tags, add a link, hit publish, repeat. Five platforms means five rounds of the same boring work.
I wanted one command: give it a topic, and it posts a unique article to every platform I have connected, then logs the result.
The shape of the tool
The design is simple on purpose. Plain Node, ES modules, no framework.
- One Markdown file per platform under
content/<topic>/<platform>.md. - A registry of "posters", one module per platform.
- An orchestrator that reads each file, builds the post, and calls the right poster.
- A CSV tracker that records every published URL.
Each poster exports the same small interface: an available(env) check and a publish() function. That way the orchestrator does not care how a platform works. It just loops.
for (const id of enabledPlatforms) {
const p = posters[id];
if (!p.available(env)) continue;
const { url } = await p.publish({ title, markdown, tags, env });
track({ platform: p.name, url });
}
Adding a new platform means writing one file. Nothing else changes.
The fun part: Markdown to Telegraph nodes
Most platforms accept Markdown or HTML. Telegraph does not. Its API wants a tree of "nodes", where each node is either a string or an object like { tag, attrs, children }. It only allows a fixed set of tags.
So I convert Markdown to HTML with marked, parse that HTML, and walk the tree. Supported tags pass through. Headings that Telegraph does not allow, like h1 and h2, get mapped down to h3. Anything unsupported, like a div, gets unwrapped so its children survive.
if (!TELEGRAPH_TAGS.has(tag)) {
out.push(...children); // hoist children, drop the wrapper
continue;
}
That single rule, hoist the children of unknown tags, fixed almost every formatting bug.
OAuth was the real work
The code was easy. Auth was not.
For Google Blogger I used the refresh token flow. The trick that cost me time: you must pass access_type=offline and prompt=consent, or Google never gives you a refresh token. I also wrote a tiny local server that catches the redirect, swaps the code for tokens, and writes them to .env automatically.
One sharp edge worth sharing. When I auto-opened the consent URL with Windows explorer.exe, the URL got cut at the first &. Google then complained that response_type was missing. The fix was to stop letting a shell touch the URL. Use execFile with an argument array, never a string. It also closes a command injection hole, so it is the correct call twice over.
Placeholders keep links consistent
Every article needs a link back to my site, with the right anchor text and tracking. I did not want to hand write that in each file. So the articles use placeholders:
[Customer Impact](https://www.customerimpact.be/?utm_source=devto&utm_medium=article&utm_campaign=backlink-builder)becomes a Markdown link with the chosen anchor text.
https://www.customerimpact.be/?utm_source=devto&utm_medium=article&utm_campaign=backlink-builderbecomes the bare tracked URL.
The orchestrator fills them in per platform and adds UTM parameters. So the link is always present, always tracked, and the anchor text can vary across platforms without editing prose.
What I would do next
A few ideas are on the list. A dedupe check so re-running a topic never double posts. A queue so I can schedule a steady cadence instead of bursts. And a generate step that drafts the per-platform variations, which I then edit by hand.
The lesson, as usual, was that the boring parts matter most. The Markdown tree walk and the OAuth edge cases took the time. The "loop over platforms" part took ten minutes.
I also added a small --only=platform1,platform2 flag early. Without it, re-running a topic would post to every connected platform again, including ones I had already published to. A simple filter in the orchestrator loop saved me from a pile of duplicate posts. If you build something similar, add that guard before you publish anything for real.
Why I built it at all
Consistency beats intensity in content. One good post a week, in the places your readers actually are, builds more than a burst once a quarter. A small tool that removes the friction makes that consistency easy to keep.
I work at Customer Impact, a growth marketing agency that helps ambitious startups and scale-ups turn strategy into measurable growth. If you want to see how a steady content engine drives real leads, take a look at what we do.