email-blast

star 9.2k

Send one-off email blasts to Gumroad creators directly via production console, no PR or deploy needed. Handles drafting, recipient query, count validation via Metabase, dry-run preview, and execution. Use when asked to "email creators", "send an announcement", "blast sellers", "notify users", or any request to send emails to Gumroad creators at scale.

antiwork By antiwork schedule Updated 4/29/2026

name: email-blast description: > Send one-off email blasts to Gumroad creators directly via production console, no PR or deploy needed. Handles drafting, recipient query, count validation via Metabase, dry-run preview, and execution. Use when asked to "email creators", "send an announcement", "blast sellers", "notify users", or any request to send emails to Gumroad creators at scale.

Email Blast

Send one-off emails to Gumroad creators by executing Ruby inline against the production console. No PR, no deploy, no service class needed.

Prerequisites

  • Production console access configured via the gumroad-prod-console skill (see its SKILL.md for setup)
  • Metabase API key (in 1Password: "Metabase (Gumroad)" → "API Key")

All production Ruby execution in this skill goes through .claude/skills/gumroad-prod-console/scripts/prod_query.sh. That script targets the read replica for MySQL, which is sufficient here — User.alive.not_suspended...find_each is a read, and deliver_later + $redis.set hit Redis, not MySQL. Do not add any MySQL-write operations to blast scripts.

Workflow

1. Clarify the request

From the human's request, extract:

  • Who — recipient criteria (e.g. "all active creators", "creators who earned > $1000")
  • What — the message to send (subject + body)
  • Reply-to — default support@gumroad.com

2. Draft the email

Write a subject and HTML body:

  • Simple HTML only: <p> tags, <a> links
  • Sign off: <p>— The Gumroad Team</p>
  • No <ul>/<li> (renders poorly). Use <p>• Item</p> for bullets.
  • Keep it short and direct
  • Always use from: "Gumroad <gumroad@creators.gumroad.com>" and sender_domain: :creators
  • The OneOffMailer template renders the subject as an <h2> heading at the top automatically

Present the draft for human approval before proceeding.

3. Count recipients via Metabase

Build a SQL query and validate the count:

METABASE_KEY=$(op item get "Metabase (Gumroad)" --vault Gumclaw --fields "API Key" --reveal)
curl -s -X POST "https://gumroad.metabaseapp.com/api/dataset" \
  -H "Content-Type: application/json" \
  -H "X-Metabase-Session: $METABASE_KEY" \
  -d '{
    "database": 2,
    "type": "native",
    "native": { "query": "SELECT COUNT(*) FROM users WHERE deleted_at IS NULL AND is_suspended = 0 AND ..." }
  }' | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['rows'][0][0])"

Always filter: deleted_at IS NULL AND is_suspended = 0 (alive, not suspended).

Show the count to the human before executing.

4. Dry run (preview)

Generate a script that sends to just 1-2 test recipients first. Run via prod_query.sh:

cat << 'RUBY' | .claude/skills/gumroad-prod-console/scripts/prod_query.sh
test_user = User.find_by(email: "sahil@gumroad.com")
OneOffMailer.email(
  email: test_user.form_email,
  from: "Gumroad <gumroad@creators.gumroad.com>",
  subject: "SUBJECT HERE",
  body: <<~HTML,
    <p>Body here.</p>
    <p>— The Gumroad Team</p>
  HTML
  reply_to: "support@gumroad.com",
  sender_domain: :creators
).deliver_later(queue: "low")
puts "Test email enqueued to #{test_user.form_email}"
RUBY

Wait for human confirmation that the test email looks good before proceeding.

5. Execute the blast

Generate the full script with Redis checkpointing for resumability:

cat << 'RUBY' | .claude/skills/gumroad-prod-console/scripts/prod_query.sh
redis_key = "email_blast_DESCRIPTIVE_KEY_last_user_id"
last_processed_id = $redis.get(redis_key).to_i
count = 0

User.alive.not_suspended.where("id > ?", last_processed_id).where(CRITERIA).find_each(batch_size: 500) do |user|
  next if user.form_email.blank?

  OneOffMailer.email(
    email: user.form_email,
    from: "Gumroad <gumroad@creators.gumroad.com>",
    subject: "SUBJECT",
    body: <<~HTML,
      <p>Body here.</p>
      <p>— The Gumroad Team</p>
    HTML
    reply_to: "support@gumroad.com",
    sender_domain: :creators
  ).deliver_later(queue: "low")

  $redis.set(redis_key, user.id)
  count += 1
  Rails.logger.info "Enqueued email for user #{user.id} (#{count} total)" if count % 100 == 0
end

puts "Done. Enqueued #{count} emails."
RUBY

6. Resume / Reset

  • Resume (if interrupted): Just re-run the same script. Redis checkpoint skips already-processed users.
  • Reset (to re-send): Delete the Redis key first:
    echo '$redis.del("email_blast_DESCRIPTIVE_KEY_last_user_id")' | .claude/skills/gumroad-prod-console/scripts/prod_query.sh
    

Safety Rules

  1. Always get human approval on subject, body, and recipient count before executing
  2. Always send a dry-run to a test recipient first (sahil@gumroad.com or the requester)
  3. Always use Redis checkpointing — blasts can be interrupted and resumed
  4. Always use deliver_later(queue: "low") — never deliver_now (would block the runner)
  5. Always filter alive.not_suspended and form_email.present?
  6. Never send from hi@customers.gumroad.com — that's for transactional emails only
  7. Log progress so you can verify emails are being enqueued

Key References

  • OneOffMailer: app/mailers/one_off_mailer.rb
  • Example service: app/services/onetime/notify_sellers_about_legacy_fee_migration.rb
  • form_email: The user's preferred contact email (may differ from login email)
  • Sender domains: :creatorscreators.gumroad.com, default → customers.gumroad.com
Install via CLI
npx skills add https://github.com/antiwork/gumroad --skill email-blast
Repository Details
star Stars 9,247
call_split Forks 1,953
navigation Branch main
article Path SKILL.md
More from Creator