Substack Publishing
Publish articles to Substack publications. Two methods: email-to-Substack (fastest, uses Himalaya CLI) and browser automation (Playwright, for full control).
Prerequisites
- Himalaya CLI installed and configured with an email account
- Substack publication with post-by-email enabled
- Playwright (optional, for browser-based publishing)
Method 1: Email-to-Substack (Recommended)
Each Substack publication has a secret post-by-email address. Find it at:
https://{publication}.substack.com/publish/settings → "Post by email"
Setup
- Configure Himalaya (one-time):
himalaya account configure
Get the Substack secret email address from the publication settings
Send a post:
cat << 'EOF' | himalaya template send
From: your@email.com
To: {publication}@substack.com
Subject: Article Title
Full article content in markdown or HTML here.
EOF
Rules for Email Posts
- Subject line = article title
- Body = full article content (Substack supports markdown and HTML)
- Images = attach as inline attachments or use hosted URLs
- First line after headers becomes the subtitle if it's wrapped in
<h2>or## - Substack auto-detects formatting and converts to their editor
Email Send Script
# scripts/send_to_substack.py
import subprocess, sys, os
def send_to_substack(publication_email, title, content_path):
with open(content_path) as f:
content = f.read()
email = f"""From: luca.dominguez.couto@gmail.com
To: {publication_email}
Subject: {title}
{content}
"""
result = subprocess.run(
['himalaya', 'template', 'send'],
input=email, text=True, capture_output=True
)
return result.returncode == 0, result.stderr
Method 2: Browser Automation (Playwright)
For full control over formatting, images, metadata, and scheduling.
Setup
playwright install chromium
Publishing Steps
- Login to Substack at
https://{publication}.substack.com/publish - Click "New Post" button
- Fill title, subtitle, body content
- Upload images via the media uploader
- Set metadata: tags, SEO title, social preview
- Schedule or Publish
Key Selectors
- Title input:
[data-testid="post-title"]or[placeholder="Title"] - Subtitle input:
[data-testid="post-subtitle"]or[placeholder="Subtitle"] - Editor body:
[data-testid="editor"]or the ProseMirror editor div - Publish button:
[data-testid="publish-button"] - Schedule button:
[data-testid="schedule-button"]
Playwright Script Template
# scripts/publish_to_substack.py
from playwright.sync_api import sync_playwright
def publish_article(publication_url, title, subtitle, content, tags=None):
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# Login (use saved cookies)
page.goto(f"{publication_url}/publish")
# ... fill in title, subtitle, content, upload images
# Publish
page.click('[data-testid="publish-button"]')
page.click('[data-testid="confirm-publish"]')
browser.close()
Post Formatting Guide
Title
- Specific, surprising, or useful
- Never clickbait
- Max 60 chars for email deliverability
Subtitle
- Tells exactly what the reader will get
- First
##or<h2>in email body becomes subtitle
Body
- Markdown preferred (Substack converts)
- Images: use hosted URLs (GitHub raw, Imgur, etc.) or attach inline
- HTML tables: Substack supports them
- Code blocks: use triple backtick with language
Metadata
- Tags: comma-separated, max 5
- SEO title: can differ from display title
- Social preview: custom image URL or auto-generated
Post-by-Email Addresses
To find the secret email for each publication:
- Go to
https://{publication}.substack.com/publish/settings - Look for "Post by email" section
- Copy the
{publication}@substack.comaddress
Each publication under your account has its own unique address.
Pitfalls
- Email-to-Substack has size limits — long posts with many images may need to be split or use browser method
- Images in email posts must be hosted URLs or inline attachments — relative paths won't work
- Drafts — email posts may go live immediately. To draft, add
[Draft]prefix to subject - Formatting — complex HTML may not render correctly. Stick to markdown
- Cookies expire — Playwright sessions need fresh cookies periodically. Use email method for reliability
- Scheduling — email method can't schedule. Use browser method for future-dated posts