← Back to Blog

Replace Mailchimp With a Claude Agent, a Free GitHub Cron, and Resend

Replace Mailchimp With a Claude Agent, a Free GitHub Cron, and Resend

Open your Mailchimp bill and look at what you are actually paying for. The number scales with how many contacts you keep, not with how often you write. Park 2,000 names and send twice a year, you still pay every month. The platform is renting you a list you built and a send button you could rebuild in an afternoon.

There are really two jobs hiding inside that subscription. One is the writing: deciding what goes in this week's email, pulling it together, making it sound like you. The other is the plumbing: storing the list, pressing send, handling unsubscribes so you stay inside the law. A newsletter platform charges you a monthly seat for both, and the price is set by the part that costs them the least.

You can split those two jobs back apart. A Claude agent does the writing. A free GitHub Actions cron runs it on a timer. Resend does the sending. The list and the unsubscribe link live in Resend too, or in a file you own. None of it needs a monthly platform fee while your list is small. Resend's free tier carries you until a single send tops about 100 people, and after that it runs $20 a month, still a fraction of what a per-contact plan charges.

Here is the whole pipeline, the honest costs, and the scrappier options if even this is more than you want to run.

What each piece actually replaces

Be clear-eyed about the division of labor, because the most common mistake is thinking Claude sends the email. It doesn't. Claude writes and decides. Something else has to hand the mail to Gmail.

The Mailchimp job Who does it now
Writer and editor A Claude agent, given a short brief and your voice
The contact list Resend Audiences, or a JSON file or KV store you own
The unsubscribe link and the law Resend's built-in unsubscribe, or your own one-click endpoint
The send button Resend's API or its Broadcasts composer
The Monday-morning timer A free GitHub Actions cron, or a Claude routine
The guardrails Claude Code hooks, so an unattended run stays on rails

Claude is the labor you used to do by hand or pay a platform's templates to rough out for you. Resend is the one part you should rent, because earning inbox trust from a cold IP is a job, not a weekend. GitHub runs the thing for free. Read that table top to bottom and you have replaced the whole product with four parts, three of which are free to start.

Step 1. Give the agent a job, not a prompt

A newsletter agent is a system prompt plus the raw material for this week. The system prompt is the part you write once. It is the job description: who the audience is, what your voice sounds like, what each issue must contain, and what it must never do.

You write the weekly email for a one-chair barbershop in Twin Falls.
Audience: regulars who booked in the last year.
Voice: plain, warm, a little dry. Short sentences. No exclamation points.
Each issue: one useful tip, one thing happening at the shop, one clear
call to book. Under 250 words. Never invent a promotion that was not in
the brief. Never use the word "valued."
Output: clean HTML, inline styles only, no <script>, no external CSS.

That prompt is the asset. It is portable, it costs nothing to keep, and it improves every time you read an issue and tighten one line. Store it in the repo next to the code.

The simplest working version calls the Anthropic API directly and hands back HTML:

// build-issue.mjs
import Anthropic from '@anthropic-ai/sdk';
import { readFileSync } from 'node:fs';

const anthropic = new Anthropic();           // reads ANTHROPIC_API_KEY
const role = readFileSync('prompts/role.md', 'utf8');
const brief = readFileSync('prompts/this-week.md', 'utf8');

const msg = await anthropic.messages.create({
  model: 'claude-sonnet-4-6',
  max_tokens: 2000,
  system: role,
  messages: [{ role: 'user', content: `This week's brief:\n${brief}` }],
});

export const issueHtml = msg.content.find(b => b.type === 'text')?.text ?? '';

That is a writer, not yet an agent. The difference is whether it can go get its own material. If you want it to read this week's blog posts, pull three rows from your sales CSV, or check what is on the calendar, you give it tools. Two ways to do that without leaving the terminal:

  • Headless Claude Code. The CLI runs a single non-interactive prompt with the -p flag, prints the result to stdout, and exits. Give it a permission mode so it doesn't stall waiting for approval in CI: claude -p "$(cat prompts/role.md)" --permission-mode acceptEdits. That mode lets it write issue.html without a prompt. If you also want it fetching its own sources unattended you need a broader mode like --dangerously-skip-permissions, which is as risky as it sounds, so scope what the agent can reach before you hand it the keys.
  • The Claude Agent SDK. The same agent harness as a library, @anthropic-ai/claude-agent-sdk for Node or claude-agent-sdk for Python (3.10+). Reach for this when the newsletter step lives inside a larger app and you want the agent loop, tools, and sessions in code.

For most small lists the plain API call is enough, and you do the "what happened this week" part by writing three bullet points into this-week.md. That file is the only thing you touch most weeks. The agent does the rest.

Step 2. Let Resend hold the list and send the mail

Resend is a developer-first email service that now carries a second surface built for exactly this. Alongside the transactional API, it ships Audiences (managed contacts) and Broadcasts (a newsletter composer). Together they are the part of Mailchimp worth keeping.

Audiences stores your contacts with email, first_name, last_name, and an unsubscribed flag, importable by CSV or API. Broadcasts sends to an audience with personalization variables, scheduling, open and click analytics, and a built-in unsubscribe footer. The unsubscribe handling is the part you must not skip and the part Resend does for you: it injects the standards-compliant List-Unsubscribe header that Gmail and Yahoo have required of bulk senders since 2024, tracks who opted out, and skips them automatically on the next send.

If your agent produced HTML, you can send it to a stored audience through the Broadcasts surface:

// send.mjs
import { Resend } from 'resend';
import { issueHtml } from './build-issue.mjs';

const resend = new Resend(process.env.RESEND_API_KEY);

const { data } = await resend.broadcasts.create({
  audienceId: process.env.RESEND_AUDIENCE_ID,
  from: 'The Chair <news@mail.yourshop.com>',
  subject: 'Booking is open for next week',
  html: issueHtml,                 // Resend appends the unsubscribe footer
});

await resend.broadcasts.send(data.id);   // or schedule it for later

Drop the {{{RESEND_UNSUBSCRIBE_URL}}} merge token into your own template if you want to control where the unsubscribe link sits. Leave it out and Resend appends its own footer. Check the Broadcasts API reference for the exact field names before you ship, since that surface evolves. If you would rather own the list yourself in a file or KV store, the self-hosted email digest post walks through the subscriber record, double opt-in, and a one-click unsubscribe endpoint, then sends through the same Resend API. Both paths end in the same place. One rents you the contact UI, the other hands you the whole thing.

One setup gate is unavoidable, and it is the same on every provider. You cannot send a single message until you verify a sending domain. Resend gives you SPF and DKIM records to add to DNS plus DMARC parameters, and verification is mandatory. Send from a subdomain like mail.yourshop.com so your main domain's reputation stays clean. The free DNS / Email Auth Audit on this site reads back your live SPF, DKIM, and DMARC so you can confirm the records landed before the first send.

Step 3. Run it on a free GitHub cron

Now the timer. GitHub Actions will run a job on a schedule for free, and for a public repository the minutes are unmetered. A private repo gets 2,000 free minutes a month on the Free plan, and a newsletter job that takes two minutes uses a rounding error of that.

# .github/workflows/newsletter.yml
name: weekly-newsletter
on:
  schedule:
    - cron: "17 13 * * 1"     # Mondays, 13:17 UTC
  workflow_dispatch: {}        # also lets you run it by hand
jobs:
  send:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci
      - run: node send.mjs
        env:
          ANTHROPIC_API_KEY:  ${{ secrets.ANTHROPIC_API_KEY }}
          RESEND_API_KEY:     ${{ secrets.RESEND_API_KEY }}
          RESEND_AUDIENCE_ID: ${{ secrets.RESEND_AUDIENCE_ID }}

Your two API keys go in the repo's Actions secrets, never in the file. Then there are four honest caveats that nobody tells you up front, and all four have a fix:

  • Cron is best-effort, not a promise. Scheduled runs can fire late, and the docs single out the top of every hour as a high-load window where queued jobs can even be dropped. That is why the example fires at :17, not :00. Offset off the hour and a daily send is reliable enough for a newsletter.
  • It auto-disables after 60 days of a quiet repo. GitHub turns off scheduled workflows when nothing has happened in the repo for 60 days. It emails you, but the job stops. If the repo is otherwise dormant, a tiny periodic commit or just opening the issue you write each week keeps it alive.
  • It runs the default branch only. The schedule always uses the workflow on the latest commit of main. Editing the YAML on a branch changes nothing until you merge.
  • Cron is UTC with no daylight saving. "8am local" drifts by an hour twice a year unless you convert and adjust, so write the comment in local time and the cron in UTC.

If you would rather not babysit a repo at all, Claude's own routines run a saved configuration on a schedule in the cloud without GitHub in the loop. The trade-offs between the two are the subject of Claude on a schedule and at scale, which covers routines, the matrix-parallel version of this for bulk jobs, and the batch discount when volume gets real.

Step 4. Hooks are the guardrails, not the clock

People hear "Claude and hooks" and assume hooks are the scheduler. They aren't, and being precise here saves you a bad week. A hook is a shell command Claude Code runs in response to a lifecycle event such as PreToolUse, PostToolUse, or Stop, configured in .claude/settings.json. Hooks react to what a session is doing. They cannot start a job on Monday morning. The cron does that. Keep the two straight: the cron initiates, hooks react.

Where hooks earn their place in this pipeline is as the seatbelt on an unattended agent. If your "writer" is full Claude Code rather than a plain API call, so it can fetch sources and write files on its own, a hook is how you keep a confused run from doing something you would regret:

  • A PreToolUse hook can refuse to run the send script unless a CONFIRM_SEND flag is set, so a test run or a wandering agent cannot blast your whole list by accident.
  • A PostToolUse hook can re-render the issue to a preview file every time the draft changes, so you always have the exact HTML to eyeball before it ships.
  • A Stop hook can commit the finished draft and append a one-line log of what went out.

The mechanics, the matcher syntax, and the exit-code rules are their own topic, worked through with copy-ready examples in the hooks implementation guide. For this pipeline the one idea that matters is the division of labor. Cron decides when. The agent decides what. Hooks decide what is and is not allowed to happen while the agent works.

What it actually costs

Here's the part the platform doesn't want you to price out. Run the numbers for a real small list.

Piece Cost
Claude drafting one issue a week Pennies per issue; under a dollar a month if you keep the brief small
Resend (free tier) $0 for 3,000 emails a month, 100 a day, 1,000 contacts
Resend (Pro) $20/month for 50,000 emails when you outgrow free
GitHub Actions cron $0 on a public repo; trivial against the 2,000 free private-repo minutes
To start About a dollar a month, mostly Claude tokens

Two honest asterisks. Claude's token cost is dominated by how much context you feed it, so a tight brief is the difference between pennies and dollars; summarize, don't paste whole article bodies. And Resend's free tier has a real ceiling: the 100-a-day cap means a single send to even 200 subscribers already exceeds it. A genuine send-to-everyone list of more than a couple hundred people puts you on the $20 Pro tier, or you spread the send across days. Mailchimp's entry Essentials plan starts at $13 a month for 500 contacts and climbs through contact brackets from there, so the comparison stays lopsided in your favor, but call the $20 what it is rather than pretending the whole thing is free forever.

The scrappier options, ranked by how little you want to run

This pipeline is the most control for the least money, but it is code you own, which means it is code you maintain. If that is more than you want, here are the honest alternatives, cheapest effort first. Prices move, so confirm each on the vendor's page before you commit.

Option Free tier Then Best for
Resend Broadcasts 3,000/mo, 100/day, 1,000 contacts $20/mo for 50K You want the agent + a managed list, minimal glue code
Buttondown First 100 subscribers From about $9/mo A writer who wants markdown and no tracking pixels, zero code
MailerLite 250 subscribers, 2,500/mo Tiered above that The closest turnkey Mailchimp swap with a drag-drop builder
Listmonk + Amazon SES Free software, SES at $0.10 per 1,000 Your hosting bill A technical owner who wants near-zero marginal cost and full ownership
Mailchimp 250 contacts, 500 sends/mo From $13/mo at 500 contacts Already on it and not ready to move

A few notes that keep you honest. Buttondown is markdown-native and privacy-first, a clean fit for an agent that already hands you finished copy, with no tracking pixels by default; its entry pricing shifted to a usage-based shape, so check the live figure. MailerLite is reducing its free tier as of July 1, 2026, so the 250-subscriber number above may already read differently by the time you look. Listmonk is genuinely free and open-source, but it is not turnkey: you self-host the app, warm up and get out of the Amazon SES sandbox, and build your own signup form and double opt-in. The $0.10-per-1,000 SES price is raw send only, not the all-in cost once you add a dedicated IP or deliverability tooling. And Mailchimp's free plan has been cut back hard over the years to 250 contacts today, so the thing most people remember as generous is now mostly a trial.

The pattern under all of these is the same one in The $20 Dollar Agency: the work an agency or a platform sells you as a monthly seat is increasingly a thing you can run yourself for the price of the tokens, if you are willing to own one small repo. The book is the long version of that argument across the whole marketing stack, not just email.

The friction, stated plainly

So you don't feel sold to, here's what you give up. You own the reliability now. If the cron misfires or a key expires, no support queue fixes it for you. You need DNS access to your domain on day one to verify the sender. Resend's Broadcasts and Audiences are deliberately simpler than Mailchimp's automation journeys, so if your business runs on multi-step drip sequences and behavioral triggers, you will hit a ceiling the agent cannot paper over. And deliverability still rides on your own domain's reputation, not on any brand. A brand-new domain with no history can land in spam no matter how clean the code is.

None of that is a reason to keep paying per contact for a list you built. It is the small print on doing it yourself, and for most one-person and small-team senders the trade is heavily worth it. You replace a recurring bill and a template editor with a short prompt, a free cron, and a sending API. Then the only thing you write each week is the part you were always going to write anyway: what is actually happening at your business.

Related reading


Fact-check notes and sources

Pricing and free-tier limits move quarterly. Confirm against the source before you commit.


This post is informational, not legal advice. Bulk and marketing email is regulated by CAN-SPAM at the federal level, by several US state privacy laws, and by GDPR and the UK GDPR for international recipients. Consult counsel for your situation. Mentions of Mailchimp, Resend, Buttondown, MailerLite, Listmonk, Amazon SES, GitHub, Anthropic, Claude, Gmail, and Yahoo are nominative fair use. No affiliation or sponsorship is implied.

← Back to Blog

Accessibility Options

Text Size
High Contrast
Reduce Motion
Reading Guide
Link Highlighting
Accessibility Statement

J.A. Watte is committed to ensuring digital accessibility for people with disabilities. This site conforms to WCAG 2.1 and 2.2 Level AA guidelines.

Measures Taken

  • Semantic HTML with proper heading hierarchy
  • ARIA labels and roles for interactive components
  • Color contrast ratios meeting WCAG AA (4.5:1)
  • Full keyboard navigation support
  • Skip navigation link
  • Visible focus indicators (3:1 contrast)
  • 44px minimum touch/click targets
  • Dark/light theme with system preference detection
  • Responsive design for all devices
  • Reduced motion support (CSS + toggle)
  • Text size customization (14px–20px)
  • Print stylesheet

Feedback

Contact: jwatte.com/contact

Full Accessibility StatementPrivacy Policy

Last updated: April 2026