Newsletter Writer Skill — /newsletter-writer
SkillCommunicationChannel-master writer for email newsletter. Weekly pipeline: scaffold structure, gather data, query the author, synthesize in their voice, review, send. Companion doc workflow. Voice-matched via the authors-voice skill. Works for any behind-the-scenes weekly newsletter built on proven content.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Newsletter Writer Skill — /newsletter-writer skill
What this skill tells your AI
The instructions your AI receives, as published by travsteward/openwriter in skills/newsletter-writer/SKILL.md and read by ahel’s review.
Weekly newsletter pipeline: scaffold structure, gather data, query the author, synthesize in their voice, review, send.
Worked example throughout: a fictional newsletter, The Sleep Brief, written by a popular-science sleep author whose community site is deeprest.example.com and whose X handle is @sleepauthor. Substitute your own newsletter name, brand, domain, and handle — they live in your project's config.md (see Configuration).
Philosophy
This newsletter is behind the scenes, not a content recap. Readers can go read the tweets and articles themselves. The newsletter gives them what they can't get anywhere else: WHY the author wrote it, what they were trying to achieve, the deeper reasoning that didn't fit in 280 characters, and how the author's project interacted with the world that week. Every section answers "what was I thinking?" not "what did I post."
Commands
/newsletteror/newsletter draft— Full pipeline: scaffold → gather → query → enhanced scaffold → draft → review/newsletter scaffold— Create doc with empty skeleton structure (header, HRs, placeholder sections, footer)/newsletter gather— Data collection only (metrics, top tweets, article)/newsletter review— Pre-send checklist + /polish 90/100 scoring on every section/newsletter send— Send via OpenWriter newsletter system/newsletter status— Subscriber count, last sent, domain status
Critical Rules
- ALWAYS use the authors-voice protocol. Load the voice profile/anchor, pull writing samples from the corpus, write with the profile as behavioral constraints, run anti-AI detection. Never skip this. The authors-voice skill ships with the OpenWriter authors-voice plugin.
- NEVER write the author's position for them. You gather data and present structure. You ASK the author what they think. They tell you. You synthesize their input in their voice. The author's original thinking is the product — you are the editor, not the writer.
- Source article content from OpenWriter docs FIRST. X Articles may not be readable via fxtwitter. The drafts live in OpenWriter. Search
list_documentsbefore trying external APIs. - Every section headline is specific to that week's content. No generic recurring labels like "This Week on X" or "The Author's Position." Each section gets a unique, attention-grabbing headline based on the actual topic. Example: "Why the 8-Hour Rule Is Wrong for Shift Workers" not "The Author's Position." "The Tweet That Got 1.75M Views About Sleep Debt" not "This Week on X." Specificity is what grabs attention.
- No outbound links except the footer CTA. The newsletter summarizes and screenshots everything — readers don't need to leave. No "View the post →" or "Read the full article →" links to X or anywhere else. The only link in the entire newsletter is the footer CTA to your brand site. Every other link is a leak.
Phase 1: Scaffold
Create the newsletter document with the empty skeleton FIRST. The author sees the structure in OpenWriter before any ideation begins. This is the visceral starting point — the bones of the newsletter visible in the editor.
Determine Issue Number
Check list_newsletter_issues for the latest sent issue number. Increment by 1.
Create & Populate Skeleton
1. create_document({ title: "The Sleep Brief #N", content_type: "newsletter" })
2. populate_document with the full skeleton below
Use a single populate_document call with this TipTap JSON structure. All section titles are placeholders — they get replaced during drafting after gather/query:
[
{"type": "heading", "attrs": {"level": 1}, "content": [{"type": "text", "text": "In this Issue:"}]},
{"type": "paragraph", "content": [
{"type": "text", "text": "Section 1"},
{"type": "hardBreak"},
{"type": "text", "text": "Section 2"},
{"type": "hardBreak"},
{"type": "text", "text": "Section 3"},
{"type": "hardBreak"},
{"type": "text", "text": "Section 4"},
{"type": "hardBreak"},
{"type": "text", "text": "Section 5"}
]},
{"type": "horizontalRule"},
{"type": "paragraph", "content": [
{"type": "text", "marks": [{"type": "italic"}], "text": "The Sleep Brief: Weekly current events and X highlights from the "},
{"type": "text", "marks": [{"type": "italic"}, {"type": "bold"}, {"type": "link", "attrs": {"href": "https://deeprest.example.com"}}], "text": "DeepRest"},
{"type": "text", "marks": [{"type": "italic"}], "text": " worldview."}
]},
{"type": "horizontalRule"},
{"type": "heading", "attrs": {"level": 2}, "content": [{"type": "text", "text": "Section 1"}]},
{"type": "paragraph", "content": [{"type": "text", "text": "[Current events or lead reflection]"}]},
{"type": "horizontalRule"},
{"type": "heading", "attrs": {"level": 2}, "content": [{"type": "text", "text": "Section 2"}]},
{"type": "paragraph", "content": [{"type": "text", "text": "[Tweet commentary]"}]},
{"type": "horizontalRule"},
{"type": "heading", "attrs": {"level": 2}, "content": [{"type": "text", "text": "Section 3"}]},
{"type": "paragraph", "content": [{"type": "text", "text": "[Tweet commentary]"}]},
{"type": "horizontalRule"},
{"type": "heading", "attrs": {"level": 2}, "content": [{"type": "text", "text": "Section 4"}]},
{"type": "paragraph", "content": [{"type": "text", "text": "[Article or thread deep dive]"}]},
{"type": "horizontalRule"},
{"type": "heading", "attrs": {"level": 2}, "content": [{"type": "text", "text": "Section 5"}]},
{"type": "paragraph", "content": [{"type": "text", "text": "[From the Archives]"}]},
{"type": "horizontalRule"},
{"type": "heading", "attrs": {"level": 2}, "content": [{"type": "text", "text": "The Reading Room"}]},
{"type": "paragraph", "content": [
{"type": "text", "marks": [{"type": "link", "attrs": {"href": "https://deeprest.example.com"}}], "text": "DeepRest"},
{"type": "text", "text": " is the home for people fixing their sleep for good. [Edition-specific CTA — echo themes]."}
]},
{"type": "paragraph", "content": [
{"type": "text", "marks": [{"type": "italic"}], "text": "The Sleep Brief is a weekly email from DeepRest."}
]},
{"type": "paragraph", "content": [
{"type": "text", "marks": [{"type": "italic"}], "text": "Powered by "},
{"type": "text", "marks": [{"type": "italic"}, {"type": "bold"}, {"type": "link", "attrs": {"href": "https://www.openwriter.io"}}], "text": "OpenWriter"}
]}
]
After scaffolding, tell the author the skeleton is live at localhost:5050. Then proceed to gather.
Workspace Ordering
All newsletter docs live in the "Newsletter" workspace. Order: most recent at top, working notes directly below its newsletter, oldest at bottom.
The Sleep Brief #N ← newest
SB#N — Working Notes
The Sleep Brief #N-1
...
The Sleep Brief #1 ← oldest
When creating the scaffold and companion doc, add both to the Newsletter workspace and use move_item with afterId to place the working notes directly after its newsletter.
Standalone /newsletter scaffold
When run standalone (not part of the full pipeline), just scaffold and stop. The author reviews the bones in the editor.
Phase 2: Gather
Collect all data before presenting anything to the author.
Tweet Metrics
If the author keeps a local tweet-metrics database (a JSON file of posts + view counts, refreshed via fxtwitter or similar), pull from it:
recent_performance[]— tweets from last 7 days sorted by viewsthreshold_crossings[]— tweets that crossed 10K/25K/50K/100K this week
If there's no metrics database, ask the author for their top 2-3 posts of the week (links or IDs) and fetch each via fxtwitter (below).
Archive Candidates (From the Archives section)
If a historical metrics database exists, search it directly for thematically relevant older posts:
// Search a tweets.json-style database for archive candidates by theme
node -e "
const arr = JSON.parse(require('fs').readFileSync('<path-to-your-tweets.json>','utf8'));
const themes = /your|regex|here/i;
const hits = (Array.isArray(arr) ? arr : Object.values(arr))
.filter(e => themes.test(e.title + ' ' + (e.topic || '') + ' ' + (e.notes || '')));
hits.sort((a,b) => (a.iterations[0]?.posted_at || '').localeCompare(b.iterations[0]?.posted_at || ''));
hits.forEach(t => {
const i = t.iterations[0];
console.log((i?.posted_at||'?') + ' | ' + t.peak_views_thousands + 'K | ' + t.title + ' | ' + (t.topic||'') + ' | ' + (i?.tweet_id||'no-id'));
});
"
Guidance:
- Build the regex from the edition's themes to find thematically relevant archive tweets
- Prefer the oldest entries — they're the founding ideas. The point of an archives section is resurfacing forgotten ideas, not recapping last month
- If no database exists, ask the author to nominate an old favorite
Top Tweet Content
Read the top 2-3 performing tweets via fxtwitter (free):
WebFetch: https://api.fxtwitter.com/{user}/status/{tweet_id}
Get full text, metrics, quoted tweet context.
Tweet Screenshots
Screenshot every tweet referenced in the newsletter (e.g. via a tweet-screenshot tool such as the x-reader skill, if installed, or any screenshot service). This is a required step — every section that references a tweet gets a visual.
Copy each screenshot into OpenWriter's image directory before inserting:
cp <screenshot-output-dir>/{tweet_id}.png ~/.openwriter/profiles/Default/_images/{descriptive-name}.png
Insert into the newsletter doc via write_to_pad using TipTap JSON:
{"type": "image", "attrs": {"src": "/_images/{descriptive-name}.png", "alt": "description"}}
Screenshot Logic: Original vs QT
For each tweet section, determine what to screenshot:
- Quote tweets → screenshot the ORIGINAL tweet (the one being quoted). Use fxtwitter to trace the quoted tweet ID. Then blockquote the author's punchiest lines from their QT below the image. This is the proven pattern:
The screenshot shows what sparked the response. The blockquote shows the author's take. The prose below delivers the WHY. This avoids screenshotting long-form QTs which render as massive images.[h2] Section Headline [img] ← screenshot of the ORIGINAL tweet being quoted [p] ← brief context line [bq] ← author's key quote from their QT response [p] ← **view count** + extended commentary [p] ← link to original post - If the original fails (deleted, protected, the screenshot service can't fetch it) → screenshot the author's FULL QUOTE TWEET instead. The QT card embeds the original tweet visually. But then trim the redundant written text — delete blockquotes of what was said and what the original said, since the screenshot already shows both. Keep only the author's extended thoughts paragraph.
- Standalone tweets (not QTs) → screenshot the tweet directly.
- Archive tweets → screenshot the archive tweet directly. Delete the blockquote copy of the tweet text — the screenshot replaces it. Keep only the context paragraphs.
Naming Convention
Use descriptive names, not tweet IDs: caffeine-study.png, sleepauthor-90min-qt.png, sleepauthor-naps.png.
Article Content
Search OpenWriter for this week's article:
mcp__openwriter__list_documents → find by date + title
mcp__openwriter__read_pad → read full content
Articles are always in OpenWriter. Do NOT rely on fxtwitter for long-form content.
Current Events Context (optional)
If the author has X API access set up (e.g. via an x-api tooling skill), pull personalized trending topics. Otherwise skip — current events context can come straight from the author in the query phase.
Phase 3: Present & Query
Present your findings, then ask questions. Do not write any content yet.
Present the Data
Show the author:
- Top tweets this week — text, views, bookmarks, likes for each
- The article — title, word count, brief description
- Archive candidates — 2-3 options for From the Archives
- Metrics summary — total views, follower count, top performer
Ask These Questions (in order)
Q1: Current Events
"What was the most prominent discussion topic in your community this week? What's the conventional viewpoint you're challenging?"
Wait for answer.
Q2: The Author's Position
"What's your position that differs from the conventional viewpoint? Give me the reasoning."
Wait for answer.
Q3: Tweet Highlights For each top tweet, ask:
"Here's your [X views] tweet: '[first line]'. Why did you write this? What were you trying to achieve? What's the thinking behind it that the tweet itself didn't say?"
Wait for answer on each.
Q4: Article Framing
"Your article '[title]' — why did you write this one? What were you trying to accomplish? What should the reader take away that goes beyond just reading the piece?"
Wait for answer (or synthesize from the article content if they defer).
Q5: Archive Pick
"For From the Archives, here are your options: [list]. Which one, and any context you want to add?"
Wait for answer.
The Position Pattern
This is the meta-pattern for any section where the author explains a position. It applies to the current events section, tweet sections, article section — anywhere the author gives their thinking.
- Author gives their position raw. Stream of consciousness, voice notes, rough explanation. They provide the THINKING — the core argument, the framing, the reasoning.
- Agent researches supporting evidence. Search for specific names, dates, facts, quotes, events that back the author's position. The author says "the melatonin supplement market exploded" — the agent finds the market size, the year-over-year growth figure, the FDA's non-regulation status. The author says "the 8-hour rule is recent" — the agent finds the pre-industrial segmented-sleep research, the historian's name, the publication year.
- Agent synthesizes position + evidence in the author's voice. The author's argument structure stays intact. The agent weaves in the specifics — names, numbers, dates, quotes — that make it concrete and credible. Then applies full authors-voice protocol (profile + samples + anti-AI).
The author provides the WHY. The agent provides the WHAT (facts) and the HOW (voice-matched prose). Never the other way around.
Phase 3.5: Companion Doc
After the query, create a companion document in OpenWriter that captures everything gathered and discussed. This is the working doc — the newsletter skeleton stays clean.
Create the Companion Doc
create_document({ title: "SB#N — Working Notes", content_type: "doc" })
Contents:
- Tweet metrics table — all recent performers with IDs, views, dates
- Author's query answers — verbatim or close, organized by section
- Research notes — supporting evidence, facts, dates gathered for each section
- Section lineup — proposed order, headlines, content assignments
- Draft iterations — refine section prose HERE before committing to the newsletter
Why this exists: If context is lost mid-build, the companion doc has everything needed to resume. More importantly, refining content shape in the companion doc BEFORE writing to the newsletter prevents gnarled implementations where early AI text marshals rewrites in the wrong direction.
Phase 4: Enhanced Scaffold
After gather and query, update the skeleton with real headlines and content assignments. The author now sees the newsletter with concrete section titles and knows what goes where before any prose is written.
Screenshots
Screenshot every tweet that will appear in the newsletter. Do this NOW, during enhanced scaffold, so images are ready for drafting. Skip if a section doesn't reference a tweet. Copy screenshots into ~/.openwriter/profiles/Default/_images/ with descriptive names (see Phase 2).
Update the Skeleton
Use read_pad to get node IDs from the Phase 1 skeleton, then write_to_pad to:
- Replace "Section 1"–"Section 5" H2 headings with crafted headlines (use /polish scoring, 90+ required)
- Replace placeholder paragraphs with one-line content assignments (e.g. "Caffeine half-life tweet — 1.75M views — afternoon-coffee angle")
- Update the "In this Issue" paragraph to match the new headlines
- Reorder sections if the data suggests a different lead
The author reviews the enhanced skeleton and approves the lineup before any writing begins.
Phase 5: Voice-Matched Drafting
Now you write. Using the author's answers as raw material, enriched with researched specifics.
Where to draft depends on whether the author has written seed content in the newsletter:
- Author wrote seed content/angles directly in the newsletter → Draft directly on the newsletter. The author's raw text anchors the direction. Apply the authors-voice protocol to polish in place.
- No seed content from the author → Draft in the companion doc first. Apply authors-voice, get approval, THEN commit to the newsletter. Never write unvetted AI text directly into a blank newsletter section — early AI prose poisons the direction of all subsequent rewrites.
Load Voice Profile
Use the authors-voice skill (ships with the OpenWriter authors-voice plugin). Load the author's anchor + NEVER rules + fingerprint per its Apply Protocol. If the author hasn't set up authors-voice yet, run its setup first — voice-matched drafting is non-negotiable for this pipeline.
Pull Writing Samples
Pull 2-3 sets of the author's writing samples matching each section's topic (from the authors-voice corpus, or recent posts/articles if no corpus exists yet). These samples are ground truth for how the author phrases things.
Craft Section Headlines
Each section gets a specific, content-driven headline — not a generic label. Use /polish (ad-legends scoring) to craft headlines that grab on specificity. Score each 0-100 on stopping power + specificity + brand fit. Don't settle below 90.
Examples of what TO do:
- "Why Shift Workers Have the 8-Hour Rule Wrong"
- "1.75M Views on the Afternoon Coffee Question"
- "The Napping Article Nobody Read (Yet)"
Examples of what NOT to do:
- "The Author's Position" / "Current Events" / "What Nobody's Saying"
- "This Week on X" / "Top Tweet"
- "The Article" / "Long Form"
Write Each Section
Apply voice profile rules as hard constraints — the specifics come from the author's authors-voice profile (diction, syntax, rhetoric, sentence distribution, contraction pattern). Treat every profile rule as a behavioral constraint, not a suggestion.
Anti-AI Detection
Run the /anti-ai skill's full check list:
- Zero em-dashes (or match author's observed frequency)
- No contrastive formulas ("It's not X, it's Y")
- No nuclear phrases ("valuable insights", "rich tapestry", etc.)
- No copula inflation ("serves as" → "is")
- Mixed contraction consistency
- Register variation (formal + casual in same piece)
Fill the Skeleton
The doc already exists from Phase 1. Use read_pad to get node IDs, then write_to_pad to replace each placeholder section with voice-matched content. Update the "In this Issue" list titles to match final headlines.
1. read_pad → get node IDs for each [Section N content] placeholder
2. write_to_pad for each section (images, paragraphs, links)
3. Update "In this Issue" paragraph with final section titles
4. Update footer CTA to echo edition themes
5. User reviews at localhost:5050
6. Iterate via write_to_pad as needed
Document Skeleton
The newsletter has a precise structure in OpenWriter. Follow this exactly.
Header Block
[h1] In this Issue:
[p] ← hardBreak-separated list of section titles (see below)
[hr]
[p] ← italics tagline (see below)
[hr]
Intro list — each item on its own line using TipTap hardBreak nodes, NOT markdown \n:
{"type": "paragraph", "content": [
{"type": "text", "text": "Section Title One"},
{"type": "hardBreak"},
{"type": "text", "text": "Section Title Two"},
{"type": "hardBreak"},
{"type": "text", "text": "Section Title Three"}
]}
Tagline — italics with bold link, sits between two HRs:
*The Sleep Brief: Weekly current events and X highlights from the **[DeepRest](deeprest.example.com)** worldview.*
Section Block (repeat for each section)
For quote tweets (most common):
[h2] Section Headline
[img] ← screenshot of the ORIGINAL tweet being quoted
[p] ← brief context line
[bq] ← author's punchiest lines from their QT response
[p] ← **view count** + extended commentary (2-4 paragraphs)
[p] ← link: [View the post →](url)
[hr]
For standalone tweets / articles:
[h2] Section Headline
[img] ← tweet screenshot or article cover
[p] ← bold callout (view count, word count)
[p] ← commentary paragraphs (2-4)
[p] ← link: [Read the full article →](url) or [View the post →](url)
[hr]
Footer Block
[hr]
[h2] The Reading Room
[p] ← CTA with link to your brand site
[p] ← *The Sleep Brief is a weekly email from DeepRest.*
[p] ← *Powered by **[OpenWriter](www.openwriter.io)*
TipTap Gotchas
- H2 headings: Markdown
## Titlevia insert often renders as empty paragraphs. Use TipTap JSON:{"type": "heading", "attrs": {"level": 2}, "content": [{"type": "text", "text": "Title"}]} - HRs: Use TipTap JSON
{"type": "horizontalRule"}for reliable HR insertion - Images: Always TipTap JSON
{"type": "image", "attrs": {"src": "/_images/name.png", "alt": "desc"}}— markdowncreates empty paragraphs - Line breaks in a paragraph: Use
hardBreaknodes, not\n
Section Strategy: Proven Content + Objection Angles
Picking Sections from Proven Content
The newsletter is built from proven content — tweets and articles that already hit. Don't invent new topics. Identify the week's top performers from your metrics database (or the author's nominations), plus archive candidates for the final section. Check OpenWriter doc metadata for tweetContext.lastPost.tweetUrl to find post URLs.
The Objection Angle
The most powerful newsletter sections don't just recap a thread or article — they address the #1 objection readers would have even if they agree with the premise.
Pattern:
- Reference the post/thread and its performance (views, engagement)
- Briefly state the core thesis (1 paragraph)
- Name the most common objection or misconception
- Dismantle it with the thinking that didn't fit in the original post
- Connect back to the brand worldview
Example: a "Sleep Debt" thread (126K views) argues lost sleep compounds and can't be fully repaid on weekends. The newsletter section isn't "here's what the thread said." It's "So Just Sleep In on Saturday?" — the #1 objection, dismantled. The section title IS the objection.
This pattern works because:
- Readers who saw the thread get NEW value (the objection addressed)
- Readers who didn't see it get the thesis + the nuance in one section
- The objection headline is a better hook than the original thesis
Image Extraction for Sections
Every section needs a visual. For articles and threads posted to X:
- Fetch the tweet via fxtwitter:
https://api.fxtwitter.com/{user}/status/{id} - Extract the image URL from the response (article cover, thread image, media)
- Download to OpenWriter images:
curl -sL "{image_url}" -o ~/.openwriter/profiles/Default/_images/{descriptive-name}.jpg - Insert via TipTap JSON in
write_to_pad
For tweet screenshots, use your screenshot tool (see Phase 2: Gather).
Phase 6: Review (/newsletter review)
Pre-send checklist. Run this before sending. Can also be invoked standalone.
Structural Checklist
Read the full newsletter via read_pad and verify every item:
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 27
- Forks
- 1
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
newsletter-writer-travsteward- Source
- github.com/travsteward/openwriter