Find accountability partner, rewritten as a state machine
Find accountability partner: when “found” means a recurring calendar event, not a conversation
Most guides on this topic stop the story at the handshake. Agree on goals. Agree on cadence. Agree to check in. On this site the verb “find” has a stricter definition, encoded in src/app/api/confirm-match/route.ts. “Found” is the state where two partners have both clicked yes on tokenized email links, Google Calendar has returned a Meet URL for a recurring event with RRULE:FREQ=DAILY, and the row in the database has flipped from confirming to pending. Until all three of those things are true together, nobody has been found yet.
The word “found” in two definitions
Toggle between the common advice-column definition and the one written into this repo. Same verb. Different terminal state. The difference is whether anything verifiable exists at the end.
Status: confirming vs. Status: pending
The auto-match cron has written a matches row pairing you with someone. Tokenized confirmation emails have been sent to both inboxes. Both person_a_confirmed and person_b_confirmed are false. No Google Calendar event exists. No Meet URL exists. Neither entry has moved past 'matched' status. The row lives for exactly 3 days before expire-matches reverts both of you to pending.
- matches.status = 'confirming'
- person_a_confirmed = false, person_b_confirmed = false
- calendar_event_id IS NULL
- 3-day fuse before expireStaleMatches(3) fires
Seven steps to actually finish finding
The cron writing a match row is step one. The partnership becoming a recurring Meet event is step six. A lot happens in between, most of it inside a single file.
Cron writes a match row
auto-match at */30 * * * * picks a viable pair, creates a matches row with status='confirming', generates two UUID tokens, and emits two emails from Resend carrying the token links.
First partner clicks yes
GET /api/confirm-match?token=<uuid>&response=yes lands. confirmerId is flipped to status='engaged'. confirmMatchPerson sets their confirmed flag. bothConfirmed returns false (the other side has not clicked). Nothing is calendared yet.
Second partner clicks yes
Same handler, same path, now bothConfirmed returns true. The branch at line 59 opens. computeSuggestedMeetUtcMinutes picks the midpoint UTC time between the two sitters' overlapping window.
OAuth refresh + Calendar POST
getAccessToken exchanges GOOGLE_REFRESH_TOKEN for a short-lived access token. createMeetEvent POSTs to calendar/v3/calendars/primary/events?conferenceDataVersion=1 with an event body carrying RRULE:FREQ=DAILY and a conferenceSolutionKey of 'hangoutsMeet'. Google returns an event ID and a hangoutLink.
meet_links rows + intro emails
Two rows inserted in meet_links, one per partner, each with a distinct tracking token. Two intro emails go out from Resend carrying the same Meet URL but routed through /meet/<token> so opens can be attributed per person.
Status flip: confirming -> pending
updateMatchStatus(match.id, 'pending') runs. Both waitlist_entries rows are updated to status='matched'. The recurring daily event now shows up on both Google Calendars, with an email invite from Google plus the introduction email from Matt. The 'find' action is terminal.
Daily RSVP poll starts watching
check-rsvp at 0 14 * * * reads attendee responseStatus fields on the calendar event each day. A 'declined' or a 404 (eventDeleted) flips status to 'ended' and closes the partnership out cleanly, without anyone having to press a button.
The two clicks, as a sequence diagram
Two humans, one internal handler, one OAuth exchange, one Calendar call. Read top to bottom, left to right. Nothing is concurrent; every step waits on the one above it.
confirm-match: the two-click handoff
What the second “yes” click actually runs
This is the branch of confirm-match/route.ts that opens only when both partners have clicked. Everything before line 59 is bookkeeping; everything after is the handoff to Google Calendar.
The try/catch around createMeetEvent is the failure fuse. If Google returns anything other than a Meet URL, the match does NOT transition to pending and the user gets a reassuring redirect even though the database still says confirming. An operator alert goes out through Resend with the subject starting “ALERT: Meet creation failed”. A human resolves it or it expires after 3 days.
The Calendar event body, verbatim
The object below is what actually crosses the wire to Google. The two fields that decide everything about what the partnership feels like for the next weeks are recurrence on line 75 and conferenceSolutionKey.type on line 79 of src/lib/google-meet.ts.
Inputs into the Calendar POST, outputs out of it
Five things flow into the moment that defines “found”. Five things flow out. None of the inputs are opinions; all of them are rows, clicks, or computed values.
inputs -> createMeetEvent -> outputs
Six facts about the calendar event
Every cell below is something the code decides once, for every partnership on the site, identically. None of it is user-tunable.
The recurrence rule is one line
recurrence: ['RRULE:FREQ=DAILY']. No end date. No byday filter. No count. The daily cadence of the whole product is this single line of event body.
Meet is not a link
conferenceSolutionKey.type = 'hangoutsMeet' plus conferenceDataVersion=1 means Google auto-generates a Meet URL as part of the event, no separate API call, no copy-paste link.
Attendees are the pair
attendees: [{ email: personA.email }, { email: personB.email }]. Google sends each of them a native calendar invite, which is how RSVPs become readable later.
sendUpdates=all
The query string carries sendUpdates=all so the invite goes out through Google's channels in addition to Matt's intro email. Two independent paths to the same event.
Calendar failure is a dead end
If the Calendar POST returns non-200, the match row is NOT flipped to pending. It stays in 'confirming'. An admin alert fires. The user's browser still sees 'confirmed' but the DB disagrees until a human intervenes.
Every terminal state is row-level
ended, expired, declined are all written to matches.status. The calendar event (deleted, 404, or declined attendee) is the signal; the match row is the ledger.
One “found”, as seen from the server log
Triggered by the second partner's yes click, this is what the handler writes to stdout while it works. Times and IDs are illustrative; the shape is what production emits.
The “found” state, in four numbers
All four are hard-coded constants in the repo, not tuned per partnership. If a claim about the product does not come back to one of these numbers, it is not load-bearing.
The number 0 is the smallest number of clicks at which the product still trusts a promise. 0 is how many external API calls it takes to materialize that promise into a calendar. 0 is how long it waits before giving up.
Every constant that fixes the “found” shape
Read left to right. Each token is directly greppable in this repo and points at the line that decides the behavior.
“One line of event body carries the whole cadence claim. There is no alternate path that produces a weekly or monthly version. Finding someone on this site means writing that recurrence rule into their calendar, and that is the definition of 'found'.”
src/lib/google-meet.ts, line 75
Why a calendar event and not a chat thread
A chat is a maybe. It contains no schedule, no duration, no record of whether anyone showed up. A recurring calendar event is a different object. It surfaces in both partners' morning agenda by default. It can be declined, forwarded, or deleted, and each of those actions produces a signal the server can read the next day through the Google Calendar API. The chat version of an accountability partnership has no such signal. You cannot tell from Slack whether your partner is still a partner; you can tell from a calendar.
This is why the check-rsvp cron exists at all. It is a daily read against attendee responseStatus on the calendar event. Values are “accepted”, “needsAction”, “declined”, and the synthetic “eventDeleted” when the event is 404. A declined or deleted state, once detected, flips the match row to “ended” and closes the loop without anyone pressing a button. The calendar is the source of truth for whether the partnership is still alive; the database mirrors it.
Everything on the practice side, what you actually do during the sit, belongs to the S.N. Goenka tradition and is taught inside 10-day residential courses. This page only covers the logistics: what the plumbing looks like between a signup form and a recurring event. The event itself is just a meeting room and a time.
Whole argument, skimmable
- This question: what does it take to find an accountability partner.
- Common answer: evaluate qualities, have a kickoff conversation, commit to a cadence.
- Product answer: two tokenized clicks into
/api/confirm-match, one OAuth refresh, one POST tocalendar/v3/calendars/primary/events. - Terminal state: a calendar event with
RRULE:FREQ=DAILYand a Meet URL on both partners' calendars. - Failure mode: Google Calendar errors out. Match stays in “confirming”. An admin alert fires. Three days later the row expires.
- Alive check:
check-rsvpreads attendee responseStatus daily. A decline or delete ends the partnership automatically. - Your effort: two minutes on the waitlist form, one click when the confirmation email lands. The rest is the server's.
- Not for: readers who want a human coach, a chat-based accountability product, or a partnership without a fixed recurring time.
Want to see what the 'found' state would look like on your calendar?
Book a short call and I will walk through what a recurring daily event pairing you with another sitter would look like, given your timezone and your preferred session length.
Frequently asked questions
On this site, at what exact moment is an accountability partner 'found'?
The moment src/app/api/confirm-match/route.ts finishes executing the branch that begins at line 59, where the variable bothConfirmed is true. Not when the cron writes a match row. Not when the first person clicks yes. Not when both people exchange a message. The specific moment is: both token clicks have landed, the match row has flipped both person_a_confirmed and person_b_confirmed to true, a POST to https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1&sendUpdates=all has come back with a non-empty hangoutLink, two rows have been inserted into the meet_links table, two intro emails have been sent through Resend, and updateMatchStatus(match.id, 'pending') plus two updateEntryStatus calls have marked both participants as 'matched'. If any single one of those operations throws, 'found' has not happened yet.
What does the 'found' state literally look like on your Google Calendar?
A daily recurring event. The recurrence field in src/lib/google-meet.ts line 75 is hard-coded to the array ['RRULE:FREQ=DAILY'] with no end date, no count, and no byday filter. The event is owned by the operator's primary calendar, the start is tomorrow at the best-overlap UTC time returned by computeSuggestedMeetUtcMinutes, and the duration is the longer of the two partners' chosen session durations (parsed at lines 171 to 184 of confirm-match/route.ts, defaulting to 60 minutes if parsing fails). Both partners are invited as attendees. Google's conferenceData.createRequest auto-generates a Google Meet room, and the video entry point URL is copied into the matches.calendar_event_id column and surfaced to each partner via a per-person tracking URL at /meet/<token>.
What if Google Calendar refuses to create the event? Is the partner still 'found'?
No. Lines 87 to 101 of confirm-match/route.ts handle that case explicitly. The match row stays at status 'confirming'. No intro email is sent. No Meet link is stored. The server fires a Resend email with the subject 'ALERT: Meet creation failed for <nameA> & <nameB>' to the operator inbox so a human can either retry or create a link manually on /admin/matching. From the user's browser the redirect still goes to /match-confirmed?response=yes, because their click was successful and the OAuth problem is not something they can fix, but in the database they are not matched and no calendar event exists. This is the one case where the page that says 'you are confirmed' is ahead of the database.
How long does 'confirming' stay open before it gives up?
Three days. The expire-matches cron at vercel.json line 12 runs at 12:00 UTC daily and calls expireStaleMatches(3) at line 11 of src/app/api/expire-matches/route.ts. That function selects every match where status = 'confirming' and created_at < now - 3 days, flips its status to 'expired', and returns both participants to the 'pending' pool with contact_count incremented by one. So the window between the cron writing your match row and needing both of you to have clicked yes is strictly three calendar days, regardless of timezone. Miss that window and your partner goes back into the pool, your contact_count is one higher than it was, and you re-enter the eligible pool after a 7-day cool-off (the cap is ten attempts before you're permanently dropped from auto-matching).
Why two confirmation clicks instead of one?
Because the operator (Matt) is not on the call. Once the recurring event is written, the product exits the loop; there is no coach watching attendance. If only one side had to confirm, the other could easily be a stale email address, a decayed intention, or an inbox that nobody checks. Requiring both tokens to click yes before any calendar event materializes is how the system extracts a weak promise from both sides simultaneously. The cost of that promise is one click each. The benefit is that the daily calendar invite does not get sent to someone who never opened the email.
What happens to the recurring event when someone stops showing up?
The check-rsvp cron at vercel.json line 15 runs once a day at 14:00 UTC, loads every match with a calendar_event_id and status in ('pending','scheduling','active'), calls the Google Calendar API to read the attendees' responseStatus fields, and writes them into calendar_rsvp_a and calendar_rsvp_b. If either field changes to 'declined' or the event returns a 404 (eventDeleted), the match status is flipped to 'ended', the event row is updated, and a summary email goes to the operator. Put plainly: if you remove the daily event from your calendar, the partnership closes out on the next daily tick. There is no manual 'end' button needed. The calendar IS the source of truth for whether the partnership is still alive.
What are the actual status transitions a match row walks through?
Five, in order. (1) confirming, written by createMatchWithTokens immediately after the auto-match cron picks a pair. (2) pending, written by updateMatchStatus on line 152 of confirm-match/route.ts once bothConfirmed is true and the Meet link has been created. (3) The individual entries move from 'matched' to 'engaged' on the first yes click (line 55) and stay at 'matched' for both after line 153-154. (4) active or scheduling can happen if the pair exchanges messages before the first sit. (5) ended, expired, or declined are terminal. The find action is only 'done' at (2), and only 'alive' until (5). Everything between pending and ended is the partnership running; everything before pending is the system still trying to finish finding.
Is there a way to manually create a match without waiting for the cron?
Yes, for the operator. The /admin/matching page can force-create a match row with status 'confirming' directly, which sends the same tokenized confirmation emails through the same confirm-match handler. This is how the first few partnerships on the site were made, before the pool was dense enough for the cron to find useful pairs. From a regular user's perspective there is no direct path: the waitlist form at /practice-buddy is the only public entry point, and it writes into waitlist_entries where the cron takes over. 'Finding', to a regular user, is always scheduled. Only an authorized operator can short-circuit the cron, and even then the two-click confirmation plus the Google Calendar step run the same way.
Why is the recurrence rule 'DAILY' and not 'WEEKLY'?
Because the product's thesis is a daily sit, not a weekly one. The form at /practice-buddy asks 'once a day' or 'twice a day' and the matcher itself builds separate morning and evening slots per person. A weekly calendar event would underbook the partnership against the stated practice cadence; a monthly event would be an attendance experiment rather than a sit. The one-line recurrence field in src/lib/google-meet.ts line 75 encodes the whole cadence claim of the product. Changing that line would change what the site is.
What stays off this page and belongs to an authorized teacher instead?
Everything about what actually happens on the cushion once the two of you open the Meet link. How to sit, how to work with a sensation, how long to practice, what to do when the mind wanders, how to handle doubt or resistance. Those questions are the province of the S.N. Goenka tradition, taught during 10-day residential courses at dhamma.org, with authorized assistant teachers. This page describes only the plumbing between 'I signed up' and 'a recurring calendar event exists.' What the calendar event contains, from the practice side, is not this site's to teach.
Comments (••)
Leave a comment to see what others are saying.Public and anonymous. No signup.