Newsletter Workflow: BookStack to Listmonk (Draft Automation)
Newsletter Workflow: BookStack → Listmonk (draft automation)
What we're automating
Publishing a newsletter is a two-app chore: the content lives in the Chronicle book of BookStack (book id 24), and the send happens in Listmonk. Right now each issue is moved by hand. This page records the process we followed for the September 2026 issue and the plan to automate the "draft it into Listmonk" half of it in n8n.
Why drafts — not scheduled sends
When a campaign is created in Listmonk with no send_at date, it is stored
as a draft and stays there until a human schedules or sends it. That is
exactly the right automation boundary:
- The hard part to automate is the boilerplate: pulling the page, cleaning the content, converting it to email HTML, and creating the campaign with the right list, template, subject, and tags.
- The human step that shouldn't be automated blindly is deciding when it goes out (and reviewing the draft first). So the automation stops at "campaign created as a draft," and a person schedules/sends it in the Listmonk UI.
- This also sidesteps a known bug: the deployed Listmonk MCP server's
schedule_campaigntool postssend_atto the/statusendpoint, which ignores it, so every schedule fails with "Campaign needs a date to be scheduled." Drafts need no date, so the automation never touches that.
The manual process (what the September 2026 issue looked like)
- Locate the issue in the Chronicle: book id 24, chapter "2026", page "September 2026 Newsletter" (page id 220 on 2026-09-07).
- Read the page's raw content. BookStack's WYSIWYG editor stores it as
markdown with
<span data-preserver-spaces="true">wrappers around nearly every block and inline run. - Strip the span tags to recover clean markdown, then convert to the
richtext HTML Listmonk wants. Verification: check for balanced
**/*, no leftover<tags>, links intact, heading levels preserved. - Create the campaign via the Listmonk MCP server with:
- name = page name ("September 2026 Newsletter")
- subject = a punchy topic phrase, matching the house style (e.g. "What's Really in Your Teacup? Part II: Beyond Organic")
- lists = [3] (Newsletter Welcome)
- template_id = 1 (Default campaign template)
- content_type = "richtext", body = the converted HTML
- tags = a consistent scheme (safe set:
newsletter, a topic tag, a date tag such asseptember-2026)
- Confirm status is
draft, with the full body intact via get_campaign.
Note on finished campaigns: Listmonk refuses to update campaigns whose
status is finished or running ("Cannot update a running or a finished
campaign"). So tags and content must be set while the campaign is a draft.
Once something has gone out you can no longer edit it — tag at creation.
MCP routes and clients used
All use the same Authorization: Basic <base64> header from
~/.hermes/config.yaml (the mcphub entry). Two front-ends exist; verify
with tools/list first because they shift:
- BookStack —
mcpserver.tea-tavern.com/servers/bookstack/sse(38 tools, get_/create_/update_* naming) - Listmonk —
mcpserver.tea-tavern.com/servers/listmonk/sse(26 tools; create_campaign, get_campaign, update_campaign, etc.) - Client:
~/.hermes/scripts/mcp_call_url.py "<url>" "$AUTH" <tool> '<json>'
The plan: automate the drafting in n8n
Goal: when a new page is created in the Chronicle, n8n drafts it into Listmonk. The flow (built from the n8n MCP server's capabilities):
- Trigger — BookStack webhook on
page_create→ n8n Webhook node. (Fallback if webhooks aren't wired: Schedule + HTTP polling of/api/pages, de-duped.) - Filter — If node: only proceed when
book_id == 24(Chronicle). - Fetch — HTTP GET
https://bookstack.tea-tavern.com/api/pages/{id}withAuthorization: Token <bookstack-api-token>. - Convert — Code node: strip the
data-preserver-spacesspans, then markdown→HTML to the richtext body Listmonk expects. - Draft — HTTP POST
https://listmonk.tea-tavern.com/api/campaignswithAuthorization: Token <listmonk-api-token>and the campaign body above, nosend_at, so it's created as a draft.
Credentials needed in n8n's store: a BookStack API token and a Listmonk API token. Open decisions: how the subject line is derived (the page name is "September 2026 Newsletter", not the punchy subject), and whether tags should be read from the page's BookStack tags instead of hard-coded.