| name | convert-newsletter |
| description | Converts Substack (or Beehiiv) newsletter posts into properly formatted blog markdown files. Use when converting newsletters to blog posts, importing Substack/Beehiiv content, or creating blog posts from newsletter URLs. |
| allowed-tools | ["Read","Write","Bash","Glob","Grep","mcp__fetch__imageFetch","WebFetch"] |
Converting Newsletter to Blog Post
A comprehensive guide for converting Substack (or Beehiiv) newsletter posts into properly formatted blog markdown files for the website.
Supported Platforms
- Substack (primary):
https://didierrlopes.substack.com/p/<slug> or https://substack.com/home/post/p-<id>
- Beehiiv (legacy):
https://didierlopes.beehiiv.com/p/<slug>
Prerequisites
- Access to the newsletter URL
- Image extraction capabilities (use mcp__fetch__imageFetch tool)
- Understanding of the blog's markdown structure and front matter requirements
Step-by-Step Conversion Process
1. Extract Newsletter Content
Use the imageFetch tool to extract content and images:
mcp__fetch__imageFetch with url=<newsletter-url> and images={"output": "file", "layout": "individual", "maxCount": 10}
Platform-specific notes:
Substack:
- Content is usually well-extracted via imageFetch in markdown mode
- For posts accessed via
substack.com/home/post/p-<id>, the content may be embedded in JSON within the HTML. Use raw=true and extract the body_html field from the embedded JSON
- Clean Substack tracking params: remove
?utm_source=didierlopes.beehiiv.com&utm_medium=newsletter&utm_campaign=... from URLs
- Substack image URLs follow the pattern:
https://substackcdn.com/image/fetch/.../https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F<uuid>_<dimensions>.<ext>
- To download high-res images, construct URL:
https://substackcdn.com/image/fetch/w_1200,c_limit,f_png,q_auto:good/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F<uuid>_<dimensions>.<ext>
Beehiiv:
- Content is directly extractable via imageFetch
- Beehiiv CDN image URLs may expire, so download immediately
Key extraction requirements:
- Capture all text content including headings, paragraphs, and lists
- Extract all images in high quality (minimum 1000px width)
- Preserve link URLs and their context
- Note any special formatting (bold, italics, quotes)
- CRITICAL: The blog post content must be 1:1 with the original newsletter (excluding ads). Do not summarize, paraphrase, or restructure. Copy the exact content, structure, and formatting.
- IMPORTANT: Use WebFetch with a prompt to extract ALL important links:
- GitHub repository URLs (especially for open source project posts)
- YouTube video URLs (these need to be embedded as iframes)
- Links to other newsletter posts (convert to internal blog links if already converted)
- Any other substantive links mentioned in the content
2. Get Publication Date
Format: YYYY-MM-DD
Substack:
- Fetch the archive page:
https://didierrlopes.substack.com/archive
- Use raw mode and extract post titles alongside dates with pattern matching
- Dates appear as short format (e.g., "Feb 24", "Mar 6") next to post titles
- The slug may contain a date suffix (e.g.,
the-era-of-on-demand-software-26-01-31) but do NOT use it - it may not match the actual publication date
- Always verify against the archive page
Beehiiv:
Rules:
- Never use today's date - always use the actual publication date
- Create slug from title: lowercase, replace spaces with hyphens, remove special characters
- Example: "The trampoline job: Optimize your career for growth" →
2025-09-19-the-trampoline-job-optimize-your-career-for-growth.md
3. Create Front Matter
---
slug: <title-slug-without-date>
title: <Full Newsletter Title>
date: <YYYY-MM-DD>
image: /blog/YYYY-MM-DD-slug/hero.webp
tags:
- <relevant-tag-1>
- <relevant-tag-2>
- <relevant-tag-3>
description: <Newsletter subtitle or first paragraph summary (max 160 chars)>
hideSidebar: true
---
Front Matter Rules:
- slug: Use title in kebab-case without the date prefix
- title: Exact newsletter title, properly capitalized
- date: Newsletter publication date in YYYY-MM-DD format (from archive page, NOT today's date)
- image: Points to the hero image path inside the post asset folder (WITH
.webp extension). If the post has no cover image, omit this field
- tags: Extract 3-6 relevant tags from content themes (lowercase)
- description: Use newsletter subtitle or create compelling summary
- hideSidebar: Set to
true for blog posts
4. Process Images
IMPORTANT: All images must be in WebP format for optimal file size and fast page loads.
Image Handling Rules:
-
Hero Image
- Substack: Fetch from the archive page (
https://didierrlopes.substack.com/archive). Extract thumbnail image URLs which follow the pattern with public%2Fimages%2F<uuid>. Download at high resolution using: https://substackcdn.com/image/fetch/w_1200,c_limit,f_png,q_auto:good/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F<uuid>_<dimensions>.<ext>
- Beehiiv: Fetch the hero image from https://didierlopes.beehiiv.com/ main page
- Some posts may use YouTube thumbnails as cover images (URL pattern:
https://substackcdn.com/image/youtube/w_728,c_limit/<video_id>)
- Download to
/static/blog/YYYY-MM-DD-slug/hero.png (or .jpg) first, then convert to WebP
- Note: The image must be saved in the post's
static/blog/YYYY-MM-DD-slug/ directory, not just /blog/
- If a post has no cover image at all, omit the
image: field from front matter
-
Content Images
- Download to
/static/blog/YYYY-MM-DD-slug/N.png (where N is sequential number)
- Download from Substack/Beehiiv CDN URLs
- Maintain aspect ratio
- Note: Save in the post's
static/blog/YYYY-MM-DD-slug/ directory
-
Convert All Images to WebP
After downloading images, convert them to WebP format using:
cwebp -q 90 "image.png" -o "image.webp" && rm "image.png"
gif2webp -q 90 "image.gif" -o "image.webp" && rm "image.gif"
Final filenames should be:
- Hero:
/static/blog/YYYY-MM-DD-slug/hero.webp
- Content:
/static/blog/YYYY-MM-DD-slug/N.webp
-
Image Markdown Format

<!-- For centered images with custom width -->
<p align="center">
<img width="500" src="/blog/YYYY-MM-DD-slug/image-path.webp" alt="Description" />
</p>
5. Convert Content Structure
Content Conversion Rules:
-
Opening Section
- Do NOT add hero image in the content (it's handled automatically via the front matter
image field)
- Include newsletter subtitle as opening paragraph
- Add
<!-- truncate --> after intro paragraph for blog preview
-
Section Dividers
- Generally not needed between sections
- Let content flow naturally without visual breaks
- Only use if there's a major topic shift that requires clear separation
-
Headings
- Newsletter H3 → Blog H2 (
##)
- Newsletter H4 → Blog H3 (
###)
- Maintain heading hierarchy
-
Lists
- Preserve bullet points as markdown lists (
-)
- Maintain indentation for nested lists
- Important: Add
<br /> after each list block for proper spacing
Example:
- First item
- Second item
- Third item
<br />
Next paragraph starts here...
-
Links
- Substack: Remove tracking parameters (
?utm_source=...&utm_medium=...&utm_campaign=...)
- Beehiiv: Convert Beehiiv tracking URLs to original URLs
- Format:
[link text](url)
- For tweets/social embeds, reference as:
This [post](url)
- Convert links to other newsletter posts to internal blog links if already converted (e.g.,
https://didierlopes.com/blog/<slug>)
-
Emphasis
- Bold text:
**text**
- Italic text:
*text*
-
Quotes and Citations
- Use blockquote syntax (
>) for extended quotes
- For multi-paragraph quotes, use
> <br /> between paragraphs
- Add
<br /> after the quote block
- IMPORTANT: Extract the actual hyperlink from the newsletter, not guess or create new ones
- Include attribution with author name and link when available
Example:
> First paragraph of the quote goes here.
>
> <br />
>
> Second paragraph of the quote continues here.
<br />
**Author Name** - ["Article Title"](https://actual-link.com)
-
Code/Technical Content
- Wrap technical terms in backticks: `term`
- Use code blocks for snippets
-
YouTube Videos
- IMPORTANT: Extract the actual YouTube URL from the newsletter content
- Convert YouTube links to embedded iframe format
- Extract video ID from URL (e.g.,
https://www.youtube.com/watch?v=VIDEO_ID → VIDEO_ID)
- Use only the video ID in the embed URL, no additional parameters
- Use responsive embed with centered layout
- IMPORTANT: Add
<br /> after the video embed so following text doesn't appear glued to it
Example conversion:
- Original:
https://www.youtube.com/watch?v=Zyw-YA0k3xo
- Embed format:
<div className="flex place-items-center justify-center items-center rounded-sm mx-auto">
<iframe
src="https://www.youtube.com/embed/Zyw-YA0k3xo"
width="800"
height="400"
/>
</div>
<br />
Note: Always verify the video link exists in the original content - don't assume or guess video IDs
-
Image Captions
- If text immediately following an image is a caption/description of that image, style it differently
- Use smaller font size and bring it closer to the image with negative margin
Example:
<p align="center">
<img width="800" src="/blog/YYYY-MM-DD-slug/image.webp" alt="Description" />
</p>
<p align="center" style={{fontSize: '0.85em', marginTop: '-0.5em'}}>Caption text describing the image above.</p>
6. Content Cleanup
Remove from Newsletter:
- Footer/unsubscribe links
- Newsletter-specific CTAs (subscribe buttons, share widgets)
- Tracking parameters from URLs (
?utm_source=...)
- Newsletter metadata (view in browser links)
- Substack "No posts" artifacts at the bottom
- "A quick note: I've moved this newsletter from Beehiiv to Substack" migration notices (unless contextually important)
Preserve:
- Author voice and tone
- All substantive content
- External references and citations
- Story flow and narrative structure
7. Quality Checks
Before finalizing:
Example Conversions
Substack URL: https://didierrlopes.substack.com/p/the-context-wars-in-financial-services
Converted to: /blog/2026-02-13-the-context-wars-in-financial-services.md
Beehiiv URL: https://didierlopes.beehiiv.com/p/the-trampoline-job-optimize-your-career-for-growth
Converted to: /blog/2025-09-19-the-trampoline-job-optimize-your-career-for-growth.md
Key transformations:
- Extracted images and converted to WebP
- Converted newsletter sections to H2/H3 headings
- Cleaned URLs of tracking parameters
- Added proper front matter with relevant tags
- Preserved personal narrative and bullet points
Common Pitfalls to Avoid
- Don't include newsletter-specific language ("Click here to read more")
- Don't forget to download images (CDN links may expire)
- Don't use relative dates ("last week") - use specific dates
- Don't include email-specific formatting (table layouts for email clients)
- Don't forget the
<!-- truncate --> marker for blog preview
- Don't trust date suffixes in Substack slugs - always verify against the archive page
- Don't use
substack.com/home/post/p-<id> URLs directly for content extraction - try didierrlopes.substack.com/p/<slug> first as it renders better