Pre-Go-Live Setup Checklist
Complete every item below before running acceptance tests. Your progress is saved automatically in this browser.
Airtable
Cal.com
OpenSign
Granola
Critical Mentions
Slack
Airtable Tables
| Table | What it's for | Filled by |
|---|---|---|
| Storytellers | Master record for every storyteller — bio, demographics, district, vetting and release status, contact info | Automation + staff |
| Sourcing Pipeline | Incoming leads before they become storytellers — Cal.com bookings, Critical Mentions hits, manual referrals | Automation + staff |
| Vetting Log | Record of every vetting check — LexisNexis, social media review, findings, outcome | Staff |
| Partner Requests | Inbound requests from partner orgs for storyteller matches — issue area, district, media tier, deadline | Staff |
| Press Pipeline | Active press pitches — outlet, reporter, pitch status, interview date, media prep links | Staff |
| Products & Lands | Completed placements — publication, date, URL, content type, audience reach | Staff |
| Retention Check-ins | Scheduled check-ins to keep storytellers engaged — due date, status, updated issue areas | Automation + staff |
| PII Review Queue | Demographic suggestions (and any flagged content) queued for staff to approve, redact, or discard before saving | Automation; resolved by staff |
| Config | System-level settings — used by automations, not for day-to-day editing | Tech |
| Goals Tracker | Per-district sourcing targets vs. live actuals — storytellers, issues, and press lands, refreshed nightly | Automation + staff |
| Donor KPIs | Program KPI snapshot for funders — reach, activation, unique storytellers used | Staff |
| Associates | Team directory — email, Slack, Cal.com link — powers routing and Herald @-mentions | Staff |
| Distribution Projects | Op-eds, LTEs, and other owned content tied to a storyteller | Staff |
| Photo Submissions | Storyteller photo uploads from the form, matched back to records | Automation + staff |
| Partner Tracker | Partner organizations and their engagement history | Staff |
| Feedback | POC acceptance-testing submissions from this dashboard | Staff |
Slack Channels
- New Cal.com intake booking
- Release form sent / signed
- Release form follow-up reminders sent
- ⚠️ PII review required
- New Critical Mentions leads added
- Inbound SMS from a storyteller
- Lambda errors
- Cron job failures
- API error rate alerts
Automations at a Glance
Key Status Values
unsigned — not yet sent
sent — awaiting signature
signed — complete
expired — lapsed, resend needed
pending — not yet vetted
approved — cleared
flagged — issue found
hold — paused, check back
new — just arrived
in_outreach — contacted
no_response — no reply yet
converted — became storyteller
declined — passed
pending — awaiting staff review
approved — ok to use
redacted — used with edits
discarded — do not use
Tools & What They Do
Goals & KPIs
Sourcing goals by district (Sage's target: 2 storytellers per priority issue per district) vs. the current storyteller bank, plus the KPI set. District counts refresh nightly (goals_actuals); in-platform KPIs refresh nightly (kpi_rollup) and grow as lands/requests are logged.
Sourcing goals by district
cell = # storytellers · target 2 · met 1 0| District | # storytellers | Medicaid | SNAP | ePTC | Tariffs | Utilities |
|---|---|---|---|---|---|---|
| AZ-01 | 8 | 4 | 0 | 0 | 0 | 1 |
| AZ-02 | 3 | 1 | 0 | 0 | 1 | 3 |
| AZ-06 | 8 | 6 | 4 | 0 | 0 | 2 |
| CO-03 | 1 | 1 | 0 | 0 | 0 | 0 |
| CO-05 | 2 | 0 | 0 | 0 | 0 | 1 |
| CO-08 | 11 | 9 | 1 | 0 | 0 | 2 |
| IA-01 | 10 | 6 | 3 | 0 | 2 | 3 |
| IA-02 | 1 | 0 | 0 | 0 | 1 | 1 |
| IA-03 | 14 | 8 | 5 | 2 | 6 | 3 |
| MI-04 | 6 | 0 | 0 | 0 | 1 | 1 |
| MI-07 | 11 | 3 | 2 | 0 | 1 | 2 |
| MI-10 | 9 | 5 | 0 | 0 | 0 | 2 |
| NC-11 | 8 | 2 | 0 | 0 | 0 | 0 |
| OH-07 | 3 | 0 | 0 | 0 | 3 | 3 |
| OH-10 | 2 | 2 | 0 | 0 | 2 | 2 |
| OH-15 | 2 | 0 | 0 | 0 | 0 | 2 |
| PA-01 | 6 | 3 | 2 | 0 | 2 | 2 |
| PA-07 | 12 | 6 | 2 | 0 | 3 | 5 |
| PA-08 | 13 | 4 | 4 | 0 | 2 | 8 |
| PA-10 | 11 | 4 | 0 | 0 | 1 | 1 |
| TX-15 | 0 | 0 | 0 | 0 | 0 | 0 |
| TX-23 | 0 | 0 | 0 | 0 | 0 | 0 |
| TX-35 | 0 | 0 | 0 | 0 | 0 | 0 |
| WI-01 | 5 | 3 | 4 | 0 | 1 | 2 |
| WI-03 | 17 | 6 | 6 | 0 | 6 | 3 |
Program KPIs — snapshot
from the 2026 analysis| Metric | Value |
|---|---|
| $ behind earned media lands | — only 8 lands have ad spend; source too sparse to total |
| Reach — new/digital media | ≈ 5.2M 2026 YTD (partial) |
| Reach — traditional media lands | ≈ 34.7M 2026 YTD · press lands with a reach value (partial) |
| Storytellers activated | 38 2026 YTD |
| Storytellers with multiple product types | 12 2026 YTD |
| Unique storytellers landed with press | 20 2026 YTD |
| Unique storytellers used by digital | 20 2026 YTD |
| Unique storytellers used by partners | — needs Sage's org-classification rule |
| Unique storytellers used by political entities (candidates, PACs, American Bridge, Porchlight) | — needs Sage's org rule |
Live in-platform counts
auto · grow as the team logs lands & requests| Metric | Value |
|---|---|
| $ behind logged ads (in-platform) | $0 auto · sum ad_spend on logged lands · updated 2026-07-22 |
| Partner requests logged | 329 auto · partner_requests · updated 2026-07-22 |
| Press lands logged (in-platform) | 1 auto · products_and_lands · updated 2026-07-22 |
| Reach — logged press lands (in-platform) | 0 auto · sum audience_reach on logged lands · updated 2026-07-22 |
| Unique storytellers landed (in-platform) | 0 auto · distinct storytellers on lands · updated 2026-07-22 |
| Unique storytellers matched to partner requests | 25 auto · distinct storytellers linked from partner_requests · updated 2026-07-22 |
Reading it: each district cell is the count of storytellers there tagged with that issue; green = target of 2 met, amber = 1, red = 0. Sage's Jul-21 KPI asks map here: reach & product counts populate from logged lands; reach-by-quarter and the reach-grows-over-time refresh need the Critical Mention saved searches; candidate / elected-official / PAC vs partner usage needs Sage's org-classification rule (marked "pending").
System Overview
The technology platform behind the CAP/CAPAF Storytelling Program — automating the storyteller lifecycle from sourcing through vetting, onboarding, distribution, and impact tracking, plus a partner-access layer. A one-page map of what's inside and what it does. 141 commits since June 5, 2026.
Architecture
All secrets in one AWS Secrets Manager entry · env-gated poc/prod (SMS + email dry-run until go-live).
What it does
Sourcing & Intake
Cal.com bookings create sourcing leads and self-assign an associate; converted leads become storyteller records; media leads flow in from hourly Critical Mentions scanning + Claude classification.
calcom_intake · lead_conversion_sync · critical_mentions_pollVetting
AI surfaces reputational concerns from associate-gathered findings as advisory notes — human-in-loop, never auto-deciding. Re-vetting alerts and Comprehend PII scanning included.
vetting_analysis · revetting_alerts · comprehend_scannerOnboarding
Welcome email with the onboarding-form link; Google-Form responses sync photos, demographics, and interests onto records; OpenSign release-form events update signing status.
onboarding_email · onboarding_form_sync · opensign_webhookDistribution — Herald
A conversational Slack bot: @-mention it to log a press pitch, a result, a land, or request a partner match. It reads the thread, plans the turn, writes the record, and pings the owning associate.
slack_events · slack_worker · claude_clientPartner Access
AI-ranked storyteller matching for partner requests (eligibility + geography filtered, issues ranked semantically) and pasted-request → outreach-draft generation.
storyteller_match · partner_outreach_draftReporting & Impact
Impact scoring per storyteller; nightly per-district goal actuals (storytellers, issues, press lands) behind the Goals & KPIs dashboard; donor KPI snapshots; retention check-ins.
impact_scoring · goals_actuals · retention_checkinsStack
How to read this. Counts are live from the repository (7,930 Python LOC across app + tests; 126 tests green). Environment is poc — SMS and email stay in dry-run until the production cutover. For per-feature status and ownership, see the Feature Status tab.
Operational Cost
What it costs to run the platform, by seat and by service. Modeled for 3 users (the two current associates + one planned hire). Slack and the EC2 host are provided through the shared IT CORE / DIGITAL CORE offerings, so they carry no marginal cost here; AWS is a usage estimate.
Per-seat licenses — scale with team size
| Platform | Annual / user | 3 users |
|---|---|---|
| Airtable | $500 | $1,500 |
| Critical Mentions | $800 | $2,400 |
| Cal.com | $150 | $450 |
| Granola | $150 | $450 |
| Per-user subtotal | $1,600 | $4,800 |
Platform & account costs — flat, regardless of team size
| Item | Annual | Notes |
|---|---|---|
| AWS platform | ~$720 | est. ~$60/mo — Bedrock (Claude Sonnet 4.5), SMS, Secrets, SES, API Gateway |
| OpenSign | $150 | flat account |
| Slack | $0 | baked in IT CORE offering |
| EC2 host | $0 | baked in DIGITAL CORE offering |
| Fixed subtotal | $870 |
Total by team size
| Team size | Per year | Per month |
|---|---|---|
| 3 users (today) | $5,670 | $473 |
| 4 users | $7,270 | $606 |
| 5 users | $8,870 | $739 |
| 6 users | $10,470 | $873 |
Efficiency & efficacy gained
Baseline today: Google Alerts arriving by email, and everything else done by hand — reading and triaging alerts, typing leads, tracking forms, logging pitches, counting KPIs. The platform runs that flow end to end — here's what it would take by hand versus with the platform, at current volume.
Hours on the flow — by hand vs. with the platform
At current volume, the operational flow that runs ~26.5 hrs/week done by hand takes about ~7 hrs/week with the platform — the system absorbs the other ~19.
| Flow | By hand (hr/wk) | With platform (hr/wk) |
|---|---|---|
| Media monitoring & lead triage (Google Alerts → read, judge, enter) | 6.0 | 1.0 |
| Interview notes → fields + demographics | 3.0 | 1.0 |
| Partner matching + outreach drafts | 3.0 | 1.0 |
| Press pipeline logging (pitch / result / land) | 2.5 | 0.8 |
| Reporting · KPIs · goals counting | 2.5 | 0.5 |
| Vetting logging + re-vet flags (AI assist; staff still decide) | 2.5 | 1.5 |
| Onboarding email + form sync + photos | 2.0 | 0.4 |
| Intake booking → record + assignment | 1.5 | 0.2 |
| Release-form tracking + reminders | 1.5 | 0.3 |
| Lead → storyteller conversion (re-typing) | 1.0 | 0.1 |
| Retention check-in tracking | 1.0 | 0.3 |
| Total (hr/wk) | ~26.5 | ~7 |
Efficacy — better outcomes, not just faster
- Coverage. Scans ~90 news items every hour, around the clock, AI-scoring each — vs. a person skimming Google Alerts a few times a day. Leads that used to slip through are now caught.
- Speed. Article → scored lead in the pipeline in ~5 minutes, any hour — vs. hours-to-next-day by hand.
- Nothing dropped. Every booking, signature, pitch, and land is logged the moment it happens; unsigned forms and overdue re-vets flag themselves.
- Better matches. Ranks 800+ storytellers by issue and geography instantly, surfacing people a manual search would miss.
- Judgment preserved. AI suggests (concerns, demographics, matches); staff always decide — speed without losing the human call.
Assumptions. Seat prices are the annual list rates you provided (Airtable $500, Critical Mentions $800, Cal.com $150, Granola $150 per user/yr; OpenSign $150/yr flat). AWS is an estimate — most of it is Bedrock for Critical Mentions, which batches 50 items per call (~1,440 calls/mo, roughly constant regardless of team size). Per-storyteller figure uses the current 824 storytellers. Slack and EC2 excluded as shared-CORE services. Efficiency figures are estimates of team-wide manual hours at current volume, valued at a $100,000/yr fully-loaded FTE (≈ $50/hr over ~2,000 productive hours) — adjust the rate or hours and the value scales linearly.
Herald — your Slack helper
🤖 AI-enabled helperHerald lives in Slack (storytellers-press) and (#storyteller-requests) channel. It powers the press and partner steps. It's fully conversational — @-mention it and say what you want in plain English, and it does the typing. Not sure where to start? Just ask. It only ever suggests and records; you always make the call.
The storytellers-press channel is shared by the Campaigns team and Press — Press mostly updates on pitches, results, and lands; anyone can ask Herald for a program number or a storyteller's bio. Same bot, same channel, one @-mention.
The #storyteller-requests channel is shared by the Campaigns team and Advocacy team. Campaigns mostly asks for partner matches; anyone can ask Herald for a program number or a storyteller's bio.
📣 Log a pitch
Tell Herald you're pitching a storyteller to an outlet. It creates the press-pipeline row and pings the owning associate.
✅ Update a result
When a pitch moves — confirmed, interviewed, published, declined — just say so and Herald updates the row.
🎉 Record a land
Drop the storyteller and the link when a piece publishes. Herald logs it and pings the liaison to track reach.
🤝 Find a match
Ask for storytellers for a partner request. Herald filters approved storytellers, AI-ranks by fit, and posts a shortlist in-thread.
📊 Ask for a number
Need a KPI or a total — reach, activation, how many approved storytellers in a state, who's most responsive? Ask in plain English and Herald queries the actual records to work it out. It never makes a figure up, and it keeps "none matched" separate from "we don't track that" so a real number never gets reported as missing.
👤 Pull up a bio
Ask for a storyteller by full name and Herald returns their bio, their press-readiness blurb, and quick facts — location, issues — ready to copy into a pitch or a partner note. It also tells you how many requests are out to them and where those stand.
🤝 Update a request
Tell Herald how a partner request is going — connected, offered as an option, placed, aired, not available — and it fills in the progress and result on the request. Say it however you like; you don't need the exact dropdown wording. Pass along anything the storyteller told you and it records that too.
🗞️ Get the roundup
Ask for this week's pitch roundup any time and Herald posts the same Monday summary right in the thread — active pitches grouped by status. No need to wait for Monday.
Good to know
- It suggests, you decide. Herald records and shortlists — it never makes the final call on a match, a status, or who gets used.
- Use the storyteller's full name. Herald matches on the real name (e.g. "Jane Doe"), so a full name lands cleanly — a bare first name may not. A name it can't confidently match is flagged for you, not guessed.
- Multiple at once. Name several storytellers in one message and it creates a row for each.
- Where. Herald works in #storytellers-press and #storyteller-requests; replies land right in the thread you tagged it from.
- Requests become placements on their own. Once a request is marked connected — or its result is placed, published, aired, or spoke — it turns into a Products & Lands row automatically, and the product type comes with it. You don't have to re-enter it.
- Nothing falls through. If a request still has no result after 20 days, Herald pings the associate on that row in #storyteller-requests to fill it in or follow up.
Herald is live and end-to-end verified. Behind the scenes it reads the thread, decides what it needs to look up, queries Airtable across the program's tables, writes back where you've confirmed it should (press_pipeline / products_and_lands / partner_requests), and @-mentions the owning associate — all from your plain-language message. It confirms before changing anything and shows you the before/after. How the AI works →
How the AI works
🤖 What it does, and what it will never doHerald is the AI part of this platform. This page explains — in plain terms — how it thinks, what stops it doing something it shouldn't, and how personal information is handled. You don't need any of this to use Herald. It's here because you should be able to check.
It used to be a phone menu
Before — matching words to a script
Herald listened for which of eight things you might mean — log a pitch, record a result, find a match, and so on — then ran the pre-written script behind that button.
If your question didn't fit one of the eight, there was no button for it, and Herald said "tell me more" — not because it misunderstood, but because nothing existed to handle it.
Every new kind of question meant building a new button.
Now — it goes and looks
Herald reads your question, decides what it would need to know to answer it, then goes and looks that up in the database — often several steps, checking one thing to work out what to check next.
Then it answers from what it actually found. Questions nobody anticipated work, because nothing has to be built in advance for them.
Ask it something new. That's the point.
Why this mattered
The old answer wasn't just unhelpful — it was wrong in a way that sounded authoritative. Someone reading it concludes the data isn't there and stops asking. That's the real cost, and it's why the AI now does the looking rather than guessing which script to run.
The guardrails
🔍 It looks before it answers
Herald answers from records it actually read. It is not allowed to describe what's on someone's record without looking it up first — including when you've just told it something yourself.
✋ It asks before it changes anything
Every change is stated first — "Setting Jane Doe's request to Connected. Confirm?" — and nothing happens until you say yes.
↩️ It shows its work, and can undo
After a change it shows the field before → after, re-read from the database rather than from memory. Say "undo that" in the same thread and it puts the old value back.
🔒 Some fields it simply cannot touch
Consent and legal fields — SMS opt-in, whether a release is signed, release status, vetting status, partner visibility — are blocked in code. Not "discouraged": refused. Those stay human.
🚫 It never contacts a storyteller
Herald has no way to send an email or a text — no such capability exists in it. Everything it does lands in Airtable or in your Slack thread, where you can see it.
🙋 It says when it can't
If something isn't possible it tells you specifically why, rather than guessing or quietly doing a near-miss instead. "I can look that up but can't change it" is a real answer.
🛡️ Personal information
Sometimes real personal detail comes up — a home address, a health detail, a date of birth. Herald is built to treat that differently from ordinary programme data:
- It doesn't put it on the record. Personal data goes to the PII review queue for a person to handle — it doesn't get quietly appended to a storyteller's file.
- It won't repeat it back. Herald refers to "that address" rather than restating the value, so it isn't copied further up the thread.
- It tells you it's there. Briefly and without lecturing — including the fact that it's now sitting in Slack, which Herald can't undo.
- It won't route around the control. It won't tell you to just go and add it in Airtable yourself, because that would sidestep a check the team deliberately built.
Being straight with you about the limits
- It can still be wrong. It's much better at saying "I don't know" than it was, and it shows the numbers it found — but for anything consequential, check the record. Treat it as a very fast colleague, not an oracle.
- "None found" isn't "we don't track it." Herald is specifically built to keep those apart and tell you which one you're getting. If it ever blurs them, that's a bug worth reporting.
- Notes aren't searchable the way tags are. Matching reads a storyteller's tags, not the free text in their notes — so something written only in notes won't pull them into a future shortlist.
- It suggests, you decide. On matches especially, Herald produces a shortlist. Who actually gets used is always a person's call.
- Anyone in the channel can talk to it. Treat a Herald thread as you would any Slack channel — it's a shared space, not a private tool.
Under the hood: Herald runs on Claude (via AWS Bedrock) with a set of defined tools for reading and writing specific Airtable tables. It reasons across several steps per question, and every write passes through a single checkpoint that validates the value, refuses blocked fields, and re-reads the record afterwards so the before/after it shows you is evidence rather than a claim.