Build and send beautiful newsletters in 3 lines of code
Project description
📬 Letterflow
Build and send beautiful newsletters in 3 lines of code.
from letterflow import Newsletter, sources
Newsletter("AI Digest").add_source(sources.ArXiv(["LLM"])).send("me@email.com")
Letterflow is a Python SDK that makes it dead simple to create automated newsletters from various content sources (arXiv, Hacker News, RSS feeds, news APIs) with AI-powered summarization.
✨ Features
- 🔌 Pluggable Sources - ArXiv, Hacker News, RSS, NewsAPI, or build your own
- 🤖 AI Summarization - Automatic summaries using OpenAI (or bring your own)
- 🎯 Relevance Filtering - AI-powered filtering to remove false positives
- 🎨 Beautiful Templates - Modern dark & minimal light themes built-in
- 📧 Multiple Senders - Gmail, SMTP, or save to file for testing
- ⚙️ Config Files - Define newsletters in YAML for easy deployment
- 🔗 Chainable API - Fluent interface for readable code
📦 Installation
pip install letterflow
# With optional dependencies
pip install letterflow[all] # Everything
pip install letterflow[arxiv,openai] # Just ArXiv + OpenAI
🚀 Quick Start
The 3-Line Newsletter
from letterflow import Newsletter, sources
Newsletter("AI Papers").add_source(sources.ArXiv(["machine learning"])).send("me@email.com")
With AI Summarization
from letterflow import Newsletter, sources, summarizers, templates
newsletter = Newsletter(
name="AI Research Weekly",
template=templates.modern, # Dark theme with gradients
summarizer=summarizers.OpenAI(model="gpt-4o-mini")
)
newsletter.add_source(sources.ArXiv(["transformer", "attention"]))
newsletter.add_source(sources.HackerNews(keywords=["AI", "GPT"], min_score=50))
newsletter.filter(topic="AI research", min_relevance=0.7)
newsletter.send(to="team@company.com")
From Config File
# newsletter.yaml
name: "AI Research Digest"
template: "modern"
summarizer:
type: "openai"
model: "gpt-4o-mini"
sources:
- type: "arxiv"
keywords: ["world models", "LLM"]
categories: ["cs.LG", "cs.CL"]
- type: "hackernews"
keywords: ["AI", "GPT"]
min_score: 100
filter:
topic: "AI and machine learning"
min_relevance: 0.6
from letterflow import Newsletter
Newsletter.from_config("newsletter.yaml").send("me@email.com")
📚 Sources
ArXiv
from letterflow.sources import ArXiv
arxiv = ArXiv(
keywords=["LLM", "transformer"],
categories=["cs.CL", "cs.LG"], # Optional: filter by category
max_results=50
)
Hacker News
from letterflow.sources import HackerNews
hn = HackerNews(
keywords=["AI", "startup"], # Optional: filter by keywords
min_score=50, # Minimum points
max_results=30
)
RSS Feeds
from letterflow.sources import RSS
openai_blog = RSS("https://openai.com/blog/rss.xml", source_name="OpenAI Blog")
NewsAPI
from letterflow.sources import NewsAPI
news = NewsAPI(
keywords=["artificial intelligence"],
api_key="your-key" # Or set NEWSAPI_KEY env var
)
Custom Source
from letterflow.sources import Source
from letterflow import Item
class MySource(Source):
name = "My Source"
def fetch(self, since=None):
# Your logic here
return [
Item(
title="Article Title",
content="Article content...",
url="https://example.com/article",
source=self.name,
published=datetime.now()
)
]
newsletter.add_source(MySource())
🤖 Summarizers
OpenAI
from letterflow.summarizers import OpenAI
summarizer = OpenAI(
model="gpt-4o-mini", # Cost-effective
api_key="sk-..." # Or set OPENAI_API_KEY env var
)
No-Op (Testing)
from letterflow.summarizers import NoOp
summarizer = NoOp() # Just truncates content, no API calls
🎨 Templates
from letterflow import templates
# Available templates
templates.minimal # Clean light theme
templates.modern # Dark theme with gradients
📧 Senders
Gmail (Recommended)
from letterflow.senders import Gmail
# Set env vars: GMAIL_ADDRESS, GMAIL_APP_PASSWORD
sender = Gmail(from_name="My Newsletter")
# Or pass directly
sender = Gmail(
address="me@gmail.com",
app_password="xxxx xxxx xxxx xxxx"
)
Setup: Create an App Password (requires 2FA)
Console (Testing)
from letterflow.senders import Console
sender = Console() # Prints to console instead of sending
File (Preview)
from letterflow.senders import File
sender = File(output_dir="./newsletters") # Saves HTML files
🔧 Environment Variables
# OpenAI (for summarization)
OPENAI_API_KEY=sk-...
# Gmail (for sending)
GMAIL_ADDRESS=you@gmail.com
GMAIL_APP_PASSWORD=xxxx xxxx xxxx xxxx
# NewsAPI (optional)
NEWSAPI_KEY=your-key
🔄 GitHub Actions
# .github/workflows/newsletter.yml
name: Daily Newsletter
on:
schedule:
- cron: '0 9 * * *' # 9 AM UTC daily
workflow_dispatch:
jobs:
send:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install letterflow[all]
- run: python send_newsletter.py
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GMAIL_ADDRESS: ${{ secrets.GMAIL_ADDRESS }}
GMAIL_APP_PASSWORD: ${{ secrets.GMAIL_APP_PASSWORD }}
🧪 Testing
# Preview without sending
newsletter.preview() # Opens in browser
# Use console sender
newsletter.send(to="test@email.com", via="console")
# Save to file
newsletter.send(to="test@email.com", via="file")
📖 API Reference
Newsletter
Newsletter(
name: str, # Newsletter name
template: Template = None, # Email template
summarizer: Summarizer = None # AI summarizer
)
.add_source(source, required=False) # Add content source
.filter(topic=None, min_relevance=0.5) # Filter by relevance
.fetch(since=None) # Manually fetch content
.summarize() # Manually run summarization
.render() -> str # Get HTML
.preview(open_browser=True) # Preview in browser
.send(to, subject=None, via=None) # Send newsletter
Item
Item(
title: str,
content: str,
url: str,
source: str,
published: datetime,
authors: list[str] = [],
summary: str | None = None,
relevance_score: float | None = None,
metadata: dict = {}
)
💡 Examples
See the examples/ directory for more:
simple.py- Basic 3-line newsletterai_digest.py- Full AI research digestcompany_newsletter.py- Multi-source company newsletterconfig_based.py- YAML config approach
📄 License
MIT License - feel free to use in your own projects!
Built with ❤️ for the newsletter automation community.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file letterflow-0.1.0.tar.gz.
File metadata
- Download URL: letterflow-0.1.0.tar.gz
- Upload date:
- Size: 23.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
56313a5f75373f007513a51e0a0ec4fd5f6cd3b9e1132d333350f763e6d0059d
|
|
| MD5 |
b0a52e1e7a14fbca8d5c0758b4d5b7e8
|
|
| BLAKE2b-256 |
8f60810a18b254ef8280b1d6f93a28b861716096feddecd5941ad9e136c1cb03
|
Provenance
The following attestation bundles were made for letterflow-0.1.0.tar.gz:
Publisher:
workflow.yml on edujuan/letterflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
letterflow-0.1.0.tar.gz -
Subject digest:
56313a5f75373f007513a51e0a0ec4fd5f6cd3b9e1132d333350f763e6d0059d - Sigstore transparency entry: 729531302
- Sigstore integration time:
-
Permalink:
edujuan/letterflow@1f2ec2436f794315a7de29a8b0d9f2c33804b5ab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/edujuan
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@1f2ec2436f794315a7de29a8b0d9f2c33804b5ab -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file letterflow-0.1.0-py3-none-any.whl.
File metadata
- Download URL: letterflow-0.1.0-py3-none-any.whl
- Upload date:
- Size: 34.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
15697b6ea065664f5f7f73955c630829ac16115b49280aafa4cc0f2afd981a2c
|
|
| MD5 |
b1e9392ca2f480cfb3bc2d07475fe820
|
|
| BLAKE2b-256 |
1d1fba35205efd978cebfc1ebad6762285a4df47dc067d81d85b489912710879
|
Provenance
The following attestation bundles were made for letterflow-0.1.0-py3-none-any.whl:
Publisher:
workflow.yml on edujuan/letterflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
letterflow-0.1.0-py3-none-any.whl -
Subject digest:
15697b6ea065664f5f7f73955c630829ac16115b49280aafa4cc0f2afd981a2c - Sigstore transparency entry: 729531314
- Sigstore integration time:
-
Permalink:
edujuan/letterflow@1f2ec2436f794315a7de29a8b0d9f2c33804b5ab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/edujuan
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@1f2ec2436f794315a7de29a8b0d9f2c33804b5ab -
Trigger Event:
workflow_dispatch
-
Statement type: