Turn every newsletter or blog post into an X thread
Convert a published article into a reviewed thread and save its permalink when it goes live.
Who it's for
Newsletter authors and developers integrating a blog or CMS publishing workflow.
What you'll have
A reusable article-to-X workflow with a stable publishing key and signed delivery notifications.
Before you start
- An active ThreadMaster subscription and a key from Settings → Developer API; set THREADMASTER_API_KEY in your environment.
- Python 3 with requests installed (python -m pip install requests). Run each recipe's Python blocks in order in the same script.
- A connected X account; threads:write, publish:write and webhooks:manage scopes.
- Your source post's stable id, title and one-line summary, plus an HTTPS webhook receiver and durable storage.
Build the workflow
Generate when your article publishes
Have your CMS run this code with the article title and one-line summary, keeping topic within 500 characters. Before scheduling, save the returned thread id with the source post id so repeat deliveries reuse the draft.
import os import requests BASE = "https://threadmaster.ai/api/public/v1" session = requests.Session() session.headers.update({"Authorization": "Bearer " + os.environ["THREADMASTER_API_KEY"]}) def api(method, path, **kwargs): response = session.request(method, BASE + path, timeout=90, **kwargs) response.raise_for_status() return response.json() post_id = "newsletter:issue-42" # Stable, unique id from your CMS title = "What we learned shipping our API" summary = "Three lessons on clear errors, previews and safe retries." thread = api("POST", "/threads/generate/", json={ "topic": (title + ": " + summary)[:500], "thread_format": "twitter" }) print(thread["id"], thread["content"]) # Persist post_id -> thread["id"] in your CMS before continuing.Review and schedule for the next morning
Review the content or apply your editorial approval policy, then schedule it for 9am tomorrow in an explicit timezone. A UUID v5 based on the source post id keeps retries tied to this publication.
from datetime import datetime, timedelta from zoneinfo import ZoneInfo from uuid import uuid5, NAMESPACE_URL tomorrow = datetime.now(ZoneInfo("America/Los_Angeles")) + timedelta(days=1) scheduled_for = tomorrow.replace(hour=9, minute=0, second=0, microsecond=0).isoformat() key = str(uuid5(NAMESPACE_URL, "https://your-site.example/posts/" + post_id + "/x")) job = api("POST", "/x/posts/", json={ "thread_id": thread["id"], "scheduled_for": scheduled_for }, headers={"Idempotency-Key": key}) print(job["id"], job["status"]) # Persist thread id, scheduled_for, key and job id with the source post.On a retry, load the saved thread id, scheduled_for and key. Idempotency applies to publishing, not draft generation.
Use fetch if your CMS runs on Node
Use fetch to schedule from Node with the same saved thread id, ISO-8601 timestamp and UUID. Supply them through environment variables to reuse the publication details from the Python example.
const response = await fetch("https://threadmaster.ai/api/public/v1/x/posts/", { method: "POST", headers: { Authorization: "Bearer " + process.env.THREADMASTER_API_KEY, "Content-Type": "application/json", "Idempotency-Key": process.env.PUBLICATION_KEY, }, body: JSON.stringify({ thread_id: Number(process.env.THREAD_ID), scheduled_for: process.env.SCHEDULED_FOR, }), }); if (!response.ok) throw new Error(await response.text()); const job = await response.json(); console.log(job.id, job.status);Register publication notifications
Register your HTTPS receiver once, before scheduling articles, so it receives publishing results. Replace the example URL and securely save the secret, which is returned only once.
webhook = api("POST", "/webhooks/", json={ "url": "https://your-site.example/threadmaster-events", "events": ["post.published", "post.failed"], "description": "Newsletter publication results" }) # Store webhook["secret"] securely as THREADMASTER_WEBHOOK_SECRET. print(webhook["endpoint"]["id"])Verify the event and save the result
Use the linked Python verify function, then save each event id once and queue its payload before returning 2xx. A worker uses the job id to find the article and saves data.permalink for post.published or data.error_code for post.failed.
import json def handle(headers, raw_body): secret = os.environ["THREADMASTER_WEBHOOK_SECRET"] if not verify(secret, headers, raw_body): # From /developers#webhooks return 401 event = json.loads(raw_body) if event["type"] in {"post.published", "post.failed"}: inbox.enqueue_once(event["id"], event) # Your durable inbox return 204Pass original raw bytes to verify before parsing JSON. Implement inbox.enqueue_once with persistent storage so the receiver can respond quickly and leave processing to a worker.
Use the documented Python or Node signature verification snippet in your receiver.
Watch out for
- Generation caps apply across your user's keys; consult the linked rate limits before importing an archive.
- Keep one saved thread per source article and one stable publishing UUID. Publishing idempotency does not deduplicate generation calls.
- The generated posts array is only a suggested X split; review the content and use the X preview before publishing when you need the final split.
- Webhook deliveries can repeat. Deduplicate envelope ids and return 2xx quickly after durable acceptance.
Check the API rate limits and quotas before running your workflow at scale.
Write your next Twitter thread or LinkedIn post today
Sign up in under a minute. $9 a month, cancel anytime.