Inspiration

Picture giving $25 to a small charity. A month later you get the same website, the same photo and the same Donate button, with no sign your gift did anything. Small charities usually have no fundraising staff to write back, so donors quietly drift away. Our deck cites that fewer than half of donors ever give again.

The community we built for is two groups at small, under-resourced charities. One is first-time and returning donors, who give modest amounts and want to know it mattered. The other is the charity’s staff: a coordinator or volunteer who is short on time and needs to see, in about 30 seconds, who to thank and who has gone quiet. We wanted giving to feel like a relationship, so we asked what it would look like if any charity’s own website remembered you and showed you what your gift did.

What it does

Chartitable is a personalization layer that any charity can drop onto its existing website with one script. A returning donor clicks a private link, which carries only a signed opaque token and no personal data. The site then shows them their own impact: a hero message, a “since your last visit” update, and a suggested next step. First-time visitors see the unmodified site.

Charities also get a staff dashboard showing who gave, what was spent and who needs a thank-you, where staff can mark donors thanked and record expenses.

Donations are pooled, so we credit each gift proportionally. Every computed number is labeled as an estimate, and logged facts are kept visibly separate from estimates. Claude writes the donor narrative, and a deterministic guard checks it against the real facts. If the guard rejects the text, or the data is thin, a plain template takes over and says less. Next steps never guilt the donor. What Claude does: Claude Sonnet 5.5 turns a donor’s giving history into a short personal note: a headline, a 2-4 sentence story, and one suggested next step. It receives a whitelisted facts payload and nothing else. That payload has no email, no donor ID and nothing about other donors. It only gets what the charity logged (records) and the donor’s proportional share of pooled gifts (estimates), kept in separate fields. Its output is constrained to a JSON schema. The prompt requires every estimate to be introduced with “about”, to be called an estimate of a pooled share, and never to appear as a total of the charity’s records.

Where it could go wrong, and what we did about it: A language model writing warmly about someone’s donation can easily overclaim: invent a number, say “your gift fed 80 meals” when gifts are pooled, add up records into a total, name someone who isn’t in the data, or slip in guilt or urgency. Prompting alone doesn’t prevent that, so a deterministic guard checks every response before a donor sees it. It rejects: any number not backed by a fact of the same kind (meals, pounds, shifts and dollars are checked separately)

  • vague quantities like “couple”, “dozens” or “half”, and any percentage
  • sentences that credit the donor directly with a pooled record count
  • any name other than the charity’s and the donor’s first name
  • pressure words such as “urgent”, “must” or “act now”
  • links, markup and emoji, and invalid output shape

The guard is built to fail safe: it accepts the occasional false rejection to avoid any false acceptance. If it rejects the text, or the data is thin, a plain template is shown instead, and the template says less. Donors never see unchecked model output. Next steps are limited to four kinds (give again, give monthly, volunteer, share), with no pressure language allowed.

How we built it

  • Engine: a pure TypeScript engine (tested with Vitest) handles allocation, milestones, tokens, narrative and email logic.
  • Backend: Convex handles allocations, tokens, dashboard queries, scheduled jobs and email.
  • Embed: the donor-facing layer is a single bundled script that fills named slots on the charity’s page, so it works with whatever site a charity already has.
  • Narrative: the narrative runs on Claude Sonnet 5.5, with the guard and template fallback behind it.
  • Email: Resend sends email, but only to demo inboxes. Payments run in test mode only.
  • Hosting and demo data: Vercel hosts the sites, served at our GoDaddy domain. We built two fictional demo charities with different looks and structures to show the layer working on two different websites. No real organizations or data are used.
  • Cursor (with Grok 4.7): Cursor, running Grok 4.7 as its model, built the donor-facing front end. That covers the layer.js embed, which reads the signed ?ct= token, stores it per charity, and fills the hero, since-last-visit and next-step slots from our donor-view endpoint. It also built the staff impact-update page and four fictional test charity websites with a preview harness. We installed Impeccable in Cursor to run automated design checks on the UI as it was written.
  • Grok Bot: we used Grok Bot to plan the work and shape the product flow and design, including how a returning donor moves from a private link to their impact page to giving again. It also helped us coordinate as a team across lanes.
  • Claude Code: an Opus orchestrator split the work into small tasks for Sonnet coding agents and read-only Haiku research and verification agents. They built the pure-TypeScript engine, the guard, the narrative pipeline, the Convex backend and HTTP API, and the staff dashboard. After every agent returned, the orchestrator read the diff, re-ran the tests, typecheck and build itself, and had Haiku check for contract drift, secrets and personal data before committing. We also used it to run readiness checks on the live deployment before the demo.

Challenges we ran into

  • Pooled money. A donation isn’t tied to one specific outcome, so we had to decide how to credit it honestly. We landed on proportional credit with every figure labeled an estimate.
  • Keeping the language model honest. Rather than trusting the prompt, we built a deterministic guard that binds each number to the unit that follows it and checks it against facts of that kind. It rejects anything the facts don’t support and falls back to a template.
  • Privacy. Personalization normally means identifying people. Our links carry only an opaque signed token, and nothing personal goes into URLs or logs.
  • Embedding on sites we don’t control. The layer had to work across different page structures and leave the site untouched when there’s nothing to show.
  • Splitting work across tools and teammates. We settled on a locked integration contract between the backend and dashboard lanes so they didn’t collide.

Accomplishments that we're proud of

  • A returning-donor experience that runs on a charity’s own site and is invisible to everyone else.
  • An honesty guard with a template fallback, so the AI feature can’t overclaim on a charity’s behalf.
  • A privacy-first design: signed opaque tokens, no names or emails in URLs, and fictional data throughout.
  • A clean separation between a pure engine and the backend, with tests.

What we learned

  • Convex: we learned how scheduled functions, a cron, file storage and typed functions fit together as a backend, and how to keep pure business logic separate from it. Our live updates use polling, so reactive queries are something we’d add next.
  • Cursor and Claude: Cursor got the donor-facing front end running quickly. The embed script went from a stub to a working layer, about 8.5 KB gzipped, that fills the page from a signed token. Claude Code handled the backend and engine, where correctness mattered more than speed. Running two coding tools in one repo only worked once we gave each tool its own files and put a locked integration contract between them (slot names, token parameter, storage key). We learned to keep agent tasks small and scoped, with each one listing the files it may and may not touch, so every diff could be reviewed. We also learned never to trust an agent’s own report. After each task the orchestrator re-ran the tests, typecheck and build, and a read-only Haiku agent checked for contract drift, secrets and personal data.

What's next for Chartitable

  • Real payments. Replace our test-mode donate page with a live payment processor, so charities can accept real gifts and gifts flow straight into each donor’s impact record.
  • Milestones across all charities. Donation milestones that link across every charity a donor gives to, so a donor’s total journey counts and not just each charity on its own.
  • Recommendations from giving history. Suggestions for where to give next, based on a donor’s previous donations.
  • Dynamic imagery. More dynamic features, starting with images that change with the donor’s impact.
  • Performance-based pricing. A small subscription fee that only starts once a charity’s giving rises more than 30% above its own average from before it adopted Chartitable. Small charities pay nothing until it demonstrably works for them.

Built With

Share this project:

Updates

Submission history