> the_problem
CryptoPrism had already solved the hard part: 1000+ coins tracked, sentiment signals generated, indicators computed, long/short calls surfaced. The data pipeline was working. The problem was distribution.
The specific constraints made this harder than a generic social automation project: post timing had to match market open windows, images had to be pixel-perfect (not approximate), caption quality needed to be consistent, and the system had to survive Instagram's session expiry logic and GitHub Actions' ephemeral CI environment.
> my_approach
The core design decision: separate content generation from publishing. Neither layer needs to know about the other's implementation.
- Jinja2 as Design System Each of the 11 post formats has a Jinja2 HTML template. The template is the single source of truth for layout, typography, and brand consistency. Change the template — every future post changes. No code required for design updates.
- Playwright for Image Generation Playwright renders each Jinja2 template to a screenshot at exact pixel dimensions (1080x1920 for stories, 1080x1080 for carousels). This is the same engine Chrome DevTools uses for screenshots — pixel-perfect, CSS-consistent, no font rendering surprises.
- GPT-4o-mini for Captions 120-150 character captions + 3-5 hashtags per post. GPT-4o-mini is fast enough (sub-2s) and cheap enough that running it 7x daily costs cents. OpenRouter provides multi-LLM fallback — if the primary endpoint is slow, it routes to an equivalent model.
- GitHub Actions as Scheduler 7 separate CRON workflows, each triggering at its scheduled UTC time. No infrastructure to maintain, no always-on server costs, no monitoring overhead. The workflow logs are the audit trail.
- Google Sheets Content Calendar A Sheets-backed content calendar gives visibility into what's scheduled without requiring database access. Non-technical stakeholders can view — and if needed, flag — upcoming content.
> architecture
CRON TRIGGERS (GitHub Actions)
02:00 UTC ──── Carousels workflow
02:30–05:30 UTC ── Stories workflows (4x)
│
▼
┌──────────────────────────────────────────────────┐
│ DATA FETCHER │
│ PostgreSQL (SQLAlchemy) → crypto data │
│ Google Sheets API (gspread) → content calendar │
│ Fear & Greed Index → BTC intelligence story │
└──────────┬───────────────────────────────────────┘
│ structured data context
▼
┌──────────────────────────────────────────────────┐
│ JINJA2 TEMPLATE RENDERER │
│ 11 templates: stories (4) + carousel slides (7) │
│ Fixed dimensions, strict CSS layout │
│ No fluid layouts — pixel precision required │
└──────────┬───────────────────────────────────────┘
│ rendered HTML
▼
┌──────────────────────────────────────────────────┐
│ PLAYWRIGHT (Chromium) │
│ Headless browser screenshot at exact px dims │
│ JPEG 95% quality output │
│ Stories: 1080x1920 / Carousels: 1080x1080 │
└──────────┬───────────────────────────────────────┘
│ image files
▼
┌──────────────────────────────────────────────────┐
│ GPT-4o-mini CAPTIONS │
│ OpenRouter API (multi-LLM fallback) │
│ 120-150 chars + 3-5 hashtags per post │
│ Retry with exponential backoff │
└──────────┬───────────────────────────────────────┘
│ images + captions
▼
┌──────────────────────────────────────────────────┐
│ INSTAGRAPI PUBLISHER │
│ 30-day session management + refresh logic │
│ Story upload / carousel upload │
│ Post metadata logged to PostgreSQL │
└──────────┬───────────────────────────────────────┘
│
▼
INSTAGRAM PUBLISHED
> hard_challenges
> results
> lessons_learned
Jinja2 + Playwright is an underrated stack for programmatic image generation. The alternative — Pillow image drawing — requires writing layout logic in Python code. Every design change is a code change. With Jinja2 + Playwright, design changes are HTML/CSS changes. The template is the design document. Designers can contribute without touching Python.
GitHub Actions CRON is surprisingly reliable for content scheduling at this scale. The main failure modes are: (1) runner startup delay of 1-5 minutes past the scheduled time — acceptable, and (2) scheduled workflows being paused after 60 days of repository inactivity — solved by a weekly keep-alive workflow that makes a trivial commit.
Session management for instagrapi deserves more upfront investment than it seems. The 30-day expiry is a hard constraint that Instagram won't negotiate, and silent auth failures are harder to debug than explicit errors. Build the refresh logic before you need it.