Automating My Morning with a Custom Daily Briefing


Daily Briefing Hero Image
Daily Briefing Hero Image

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.sock

Conclusion

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