Skip to main content

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 newsletter
  • ai_digest.py - Full AI research digest
  • company_newsletter.py - Multi-source company newsletter
  • config_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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

letterflow-0.1.0.tar.gz (23.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

letterflow-0.1.0-py3-none-any.whl (34.1 kB view details)

Uploaded Python 3

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

Hashes for letterflow-0.1.0.tar.gz
Algorithm Hash digest
SHA256 56313a5f75373f007513a51e0a0ec4fd5f6cd3b9e1132d333350f763e6d0059d
MD5 b0a52e1e7a14fbca8d5c0758b4d5b7e8
BLAKE2b-256 8f60810a18b254ef8280b1d6f93a28b861716096feddecd5941ad9e136c1cb03

See more details on using hashes here.

Provenance

The following attestation bundles were made for letterflow-0.1.0.tar.gz:

Publisher: workflow.yml on edujuan/letterflow

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for letterflow-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 15697b6ea065664f5f7f73955c630829ac16115b49280aafa4cc0f2afd981a2c
MD5 b1e9392ca2f480cfb3bc2d07475fe820
BLAKE2b-256 1d1fba35205efd978cebfc1ebad6762285a4df47dc067d81d85b489912710879

See more details on using hashes here.

Provenance

The following attestation bundles were made for letterflow-0.1.0-py3-none-any.whl:

Publisher: workflow.yml on edujuan/letterflow

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page