Automating My Morning with a Custom Daily Briefing
The Problem: Information Overload
Every morning, I found myself jumping between tabs—Hacker News, weather reports, market prices, and RSS feeds. It’s a fragmented way to start the day. I wanted something analog, physical, and focused. I wanted a “morning paper” tailored specifically to my interests, delivered without me lifting a finger.
The Solution: Daily Briefing
I built Daily Briefing, a containerized Python service that aggregates data, generates a beautiful 8.5” x 11” B&W PDF, and sends it directly to my home printer.
Prerequisites:
- Docker and Docker Compose.
- A printer accessible via CUPS (Mac/Linux) or shared on your network.
- Basic knowledge of Python and environment variables.
Key Features
- Automated Data Fetching: Pulls from Open-Meteo, CoinGecko, and curated RSS feeds.
- Morning Brain Food: “Word of the Day,” historical facts, and daily quotes to start the day smart.
- Daily Fun: A fresh Sudoku puzzle and the latest xkcd comic (auto-grayscaled).
- Always-On Docker Service: Runs as a background daemon with internal daily scheduling.
- Direct Printing: Uses a bridged CUPS socket to print directly from a container.
Technical Deep Dive
1. The “Single Page” Constraint
The biggest challenge was ensuring the briefing always fits on a single 8.5” x 11” sheet. I used WeasyPrint for the PDF engine because it supports modern CSS like @page size and Flexbox.
The layout uses a two-column grid where the “Sidebar” (Weather, Markets, Puzzle) has a fixed width, and the “News” column uses flex-grow to fill the remaining space. I implemented a summary truncation logic in Python to cap news snippets at 140 characters, ensuring they never overflow the page.
2. Python-Based Scheduling
Instead of relying on the host’s cron, I moved the scheduling logic into the container using the Python schedule library. This makes the entire project a portable “Always-On” service.
import schedule
import time
def main():
schedule.every().day.at("07:00").do(run_job)
while True:
schedule.run_pending()
time.sleep(60)3. Bridging the CUPS Socket
To let Docker talk to my physical printer, I mounted the host’s CUPS socket into the container. This allows the container’s version of lp to see all the printers configured on my Mac:
volumes:
- /var/run/cups/cups.sock:/var/run/cups/cups.sockConclusion
Building this project wasn’t just about code; it was about reclaiming my morning routine. Now, instead of staring at a screen for 20 minutes, I have a physical page that gives me everything I need to know before I even open my laptop.
References
Repositories & Tools
APIs Used
About the author

Written by Nicholas Diesslin Pizza acrobat 🍕, typographer, gardener, bicyclist, juggler, senior developer, web designer, all around whittler of the web.
Follow Nick on Twitter!