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

  1. 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.
  2. 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.

  3. 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);
  4. 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"])
  5. 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 204

    Pass 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.

Get started

Write your next Twitter thread or LinkedIn post today

Sign up in under a minute. $9 a month, cancel anytime.

Get started →