Skip to content
Back to endeavors
Aye Pasquale logo

Aye Pasquale

A dog's recipe newsletter, powered by AI

  • Python
  • OpenAI
  • Neon
  • Cloudflare Workers
  • Cloudflare Pages
  • GitHub Actions
  • SMTP2Go

The idea

My grandpa was a heavy, mobster-lookin’ Italian guy who owned a pizza shop. Sundays meant full Italian dinners at his house — him in the kitchen mixing salads, making meatballs, tossing pizza dough over his head. When I’d walk in, he’d shout: “Aye! Pasquale!” I named my dog after that. The newsletter writes recipes in that voice: an Italian grandpa dog who’s way too excited about food.

Who it’s for

  • The subscriber. Gets a daily or weekly recipe email from a dog who talks like an Italian grandpa. Opens it for the voice, stays for the recipes. Manages their subscription through a magic-link portal without needing a password.
  • Friends and family. The original audience. They know Pasquale, they know the grandpa voice, they forward the emails.
  • Me. Wanted to wire AI up to something real, not just a chatbot. A system that runs on its own and produces something people actually enjoy reading.

AI content pipeline

Problem: I wanted to explore how AI actually works by wiring it up to something real. Not just a chatbot, but a system that runs on its own and produces something people enjoy.

Solution: Built a Python script that fetches a random recipe from Spoonacular, sends it to OpenAI (GPT-3.5-turbo-16k) to rewrite in Pasquale’s voice. The prompt gives the AI full character context: Pasquale is an Australian Shepherd born June 23, 2022, who writes from a dog’s perspective with an Italian grandpa accent. He analyzes ingredients for dog safety, riffs on cooking techniques and occasionally answers reader questions in character. The script generates magic tokens for each subscriber, builds personalized HTML emails with Pasquale’s signature and social links, then sends via SMTP2Go.

Outcome: The newsletter runs daily via GitHub Actions at 8 AM EST. Each send is logged to a email_send_log table with success/failure status. A test workflow lets me preview emails before they go to the full list.

Airtable + Softr + Windows Task Scheduler

Problem: The first version needed somewhere to store subscribers and a way to run on a schedule. I cobbled together what I knew.

Solution: Airtable for subscriber data, Softr for a management portal with magic links, and Windows Task Scheduler to trigger the Python script daily.

Outcome: It worked, but it was brittle. Tied to a Windows machine being on, and Softr’s magic link system was clunky.

Supabase migration

Problem: I wanted a real database, proper auth, and something that wasn’t dependent on my laptop running.

Solution: Migrated subscriber data to Supabase with PostgreSQL, edge functions, and Row Level Security. Set up GitHub Actions to replace the Windows scheduler.

Outcome: Hit Supabase’s free project limit almost immediately.

Neon + Cloudflare Workers

Problem: Needed a new database host that wouldn’t cap out, and a proper API layer for the subscriber portal.

Solution: Moved to Neon for serverless PostgreSQL and built a Cloudflare Workers API in TypeScript. The API handles seven endpoints: subscribe, unsubscribe (RFC 8058 one-click with SHA-256 hash verification), magic link requests, token validation, preference updates, email changes and magic link sending. Built a migration script with dry-run mode, CSV import, email validation and status normalization to move 44 subscribers cleanly from Airtable.

Outcome: 44 subscribers migrated, daily sends running in production, and an infrastructure stack that costs nothing at current scale.

Subscriber portal

Problem: Subscribers needed a way to manage their preferences without emailing me. Softr’s portal was slow and limited.

Solution: Built a vanilla HTML/CSS/JavaScript portal on Cloudflare Pages. No framework, no dependencies. The subscribe page takes an email, frequency (daily or weekly) and timezone. The manage page is magic-link protected: subscribers click a link in their email, and the token (valid for 7 days) unlocks a dashboard where they can change frequency, update their timezone, switch email addresses with a verification flow, or unsubscribe. Collapsible sections keep the UI clean. ARIA labels and keyboard navigation are built in.

Outcome: The portal lives at ayepasquale.com. Subscribers manage everything themselves. I don’t touch subscriber data manually.

Email compliance and delivery

Problem: Email clients are strict about unsubscribe handling. If the emails don’t follow the spec, they end up in spam or get flagged.

Solution: Every email includes RFC 8058 List-Unsubscribe headers with both a one-click HTTPS endpoint and a mailto fallback. The one-click endpoint verifies a SHA-256 hash of the subscriber ID, email and a secret before processing. SMTP2Go handles delivery and bounce management. The Workers API sends three types of templated emails: management magic links (7-day expiry), email change verification (24-hour expiry), and welcome messages for new subscribers.

Outcome: Emails land in inboxes, not spam. One-click unsubscribe works in Gmail, Apple Mail and Outlook. The whole system is self-sustaining. I just watch the logs.

Database design

Problem: The system needed to track subscribers, auth tokens and send history reliably across Python scripts and Cloudflare Workers.

Solution: Three tables in Neon PostgreSQL. subscribers stores email, status (Active, Tester, Unsubscribed, Bounced), send frequency, timezone, and send dates. magic_tokens handles 7-day expiring auth tokens. email_send_log tracks every send attempt with status and error messages. Eleven stored procedures handle the business logic: fetching subscribers due for email, batch-updating send dates, token validation, preference updates, cleanup of expired tokens, and a 90-day retention policy for send logs.

Outcome: Business logic lives in PostgreSQL, not scattered across Python and TypeScript. Both the newsletter script and the Workers API call the same stored procedures.