ClaudeLoops
/
Weekly investor update generator
All loops
FinanceEasy 10 min· claude-sonnet-4-5

Weekly investor update generator

Stripe + Postgres metrics → narrative artifact → Resend to your investor list.

NOT DEPLOYEDNOT DEPLOYED
0187ms
Trigger
cron(0 7 * * *) fired · every day · 07:00
021267ms
Agent
claude-sonnet-4-5 · in 1167 tok · out 588 tok
03297ms
Tools
postgres-mcp/anthropic:messages.create → 429 backoff · 227ms
0477ms
Verify
schema check · zod v3 passed
05167ms
Output
invoice row upserted · $4,120
0647ms
Notify
audit log written · runbook link attached
SUCCESS
0%
0 runs
P50
0ms
median
P95
0ms
tail
AVG COST
per run
LAST OK
never
LAST FAIL
never
none
Latency · last 30 runs0 samples
no runs yet
Latest output · what your users see
Invoice · SegmentTooling
$3,787.87 USD
// press Test to run once · Watch live to keep streaming · Deploy to make it real
The problem

Investor updates slip every week because no one wants to write them at 11pm Sunday.

The outcome

An email lands in your investors' inbox every Monday at 09:00. You proofread it Sunday afternoon in three minutes.

Ingredients & skills

Secrets
  • ANTHROPIC_API_KEY
  • STRIPE_KEY
  • DATABASE_URL
  • RESEND_API_KEY
Providers
  • Anthropic
  • Stripe
  • Postgres
  • Resend
MCP servers
  • postgres-mcp
  • stripe-mcp
#fundraising#artifacts#cron

How it works

Cron job pulls MRR from Stripe and active users from Postgres, asks Claude to write a 200-word narrative artifact, and emails it via Resend.

Step 1

1 — Pull the numbers

Two source-of-truth queries. Don't trust derived metrics dashboards for the email.

metrics.ts
import Stripe from "stripe";
import { Client } from "pg";

export async function pullWeeklyMetrics() {
  const stripe = new Stripe(process.env.STRIPE_KEY!);
  const subs = await stripe.subscriptions.list({ status: "active", limit: 100 });
  const mrr = subs.data.reduce((s, x) => s + (x.items.data[0].price.unit_amount ?? 0) / 100, 0);
  const db = new Client({ connectionString: process.env.DATABASE_URL });
  await db.connect();
  const { rows } = await db.query("select count(*)::int as wau from events where ts > now() - interval '7 days' group by user_id");
  await db.end();
  return { mrr, wau: rows.length };
}
Step 2

2 — Narrative artifact

Constrain the model to a fixed structure. Investors hate variance week to week.

typescript
const prompt = `Write a 200-word investor update.\nFormat: 1 sentence headline, then bullets for Wins, Risks, Asks.\nMRR=$${metrics.mrr}, WAU=${metrics.wau}.\nRecent changelog:\n${changelog}`;
Step 3

3 — Send via Resend

BCC the investor list so replies stay private.

typescript
await fetch("https://api.resend.com/emails", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.RESEND_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({ from: "ceo@acme.com", bcc: investors, subject: `Update — week of ${weekOf}`, html: rendered }),
});
One-line deploy

The button above runs the same command with your saved config. This is the raw CLI form.

bash
locker schedule investor-update agent.ts --cron '0 9 * * 1'

Related loops