Accountability, without the app
Accountability Partner Apps: The One That Ships No App
Most accountability partner apps treat the app as the product: streaks, push notifications, chat UIs, coaches. Practice Buddy does the opposite. It is a matcher that pairs meditators by 60-minute UTC session slot, hands over a permanent Google Meet URL, and gets out of the way. This page walks through the actual cron, line by line.
TL;DR
Accountability partner apps typically sell a mobile app with streaks, push notifications, and chat. Practice Buddy (at /practice-buddy on this site) ships no app. It is a server cron that builds per-session UTC match slots, applies a ±60 minute hard filter, greedily pairs them, provisions a permanent Google Meet URL via Google Calendar, and sends a single intro email. Same link every day, forever. Every mechanism on this page is pulled from the real source at src/app/api/auto-match/route.ts. Free, in the spirit of dana.
The accountability partner that is not an app
No download. No account. No streak. Just a matcher, an email, and a permanent Meet link pinned at the session level, not the person level.
- Built for meditators who already have one too many apps on their phone.
- Matching runs on a 60-minute UTC window at the per-session level, so twice-a-day sitters can hold two different buddies.
- Every pair gets a permanent Google Meet URL on match confirmation. The URL never changes.
- The site's only job is the form. After that, it is Gmail and Google Meet and a human reviewing pairs.
apps to install
UTC match window, hard
session slots per twice-a-day sitter
permanent Meet URL per pair
Every post on the first page of Google for accountability partner apps is a listicle of products with the same features: a mobile app, a streak counter, push notifications, an in-app chat, sometimes a money-on-the-line mechanic. If you are trying to work on a laptop, that stack is useful. If you are trying to sit cross-legged on a cushion for an hour at 6 AM, a notification badge on your phone at 5:58 AM is drag, not support. The only thing that actually improves whether you sit is another person who is already waiting for you in a video call.
That is the narrow claim this page is about. Not all accountability apps are useless, not all streak counters are bad. But for a daily meditation practice, the right accountability product is not an app at all. It is an email, a permanent Google Meet URL, and a person on the other end. The rest of this page is the audit of how the matcher on this site actually builds that pair.
The apps this one is not
The neighbors in the SERP. Every one of these is a real product, and several of them are good at what they do. None of them are built for a meditator with a 60-minute daily sit and a 90-minute rule about screens before bed. That is the gap.
Inputs, matcher, outputs
The whole stack on one diagram. The form on the left collects six fields. The matcher in the middle is a single cron route at /api/auto-match. The outputs on the right are delivered by email and a redirect. There is nothing between the matcher and the meditator except an inbox.
Matcher
SessionSlot[]
greedy pair
±60 min UTC
Everything on the right side is delivered by email and a redirect. No app, no account, no SDK, no push notification service.
Four mechanics that keep the pool clean
The bento below is what replaces a moderation team, a reputation system, and a block list in a typical accountability app. The top card names the anchor fact of this entire design, the session-level match. The three smaller cards are the quieter guards around it.
Anchor fact
Sixty-minute UTC hard filter, at the session level
The matcher does not pair people. It pairs sessions. A “Twice a day” sitter generates two SessionSlot objects, morning and evening, each keyed as `${personId}:${session}`. A pair is viable only when timeDiff(a, b) ≤ 60 UTC minutes. Slots are then consumed greedily by a sort that puts “both ready” first, then “both old students,” then same duration, then smallest time gap. One person can end up with two different buddies.
Guard
Serial-ghoster cap
After contact_count ≥ 2 the person is quietly dropped from the eligible pool. No email is sent. No waitlist lifetime. No punishment points.
Anti-spam
24-hour cooldown before the first match
A pending user is not matched until they have been in the pool for at least one day. That is how long the operator takes to eyeball the signup and catch a bot or a duplicate.
Return path
Declined matches cost nothing and prior pairs are blocked forever
When a match expires or is declined, both people go back to the pool. A blockedPairs set rebuilt each cron run guarantees the same two people will not be offered to each other again. If a match confirms, a Google Calendar event is created and a permanent Meet URL is issued through createMeetEvent(). The URL does not change day to day.
The matcher, step by step
Seven steps, pulled verbatim from the Typescript in /src/app/api/auto-match/route.ts. The cron runs every two hours, defaults to dry run (reports matches it would make without sending emails), and only goes live when called with ?live=true. Every query and every conditional below is what actually runs in production.
Pull the eligible pool
A Postgres query grabs every row from waitlist_entries where status is 'pending' or 'ready' and unsubscribed is false, ordered so 'ready' people (already confirmed once before) jump to the front.
SELECT id, name, email, timezone, city, frequency,
session_duration, morning_time, morning_utc,
evening_time, evening_utc, is_old_student,
status, contact_count, pass_count, created_at,
unsubscribed, unsubscribe_token
FROM waitlist_entries
WHERE status IN ('pending', 'ready')
AND unsubscribed = false
ORDER BY CASE status WHEN 'ready' THEN 0 ELSE 1 END,
created_at ASC;Filter by eligibility rules
Drop anyone with contact_count >= 2 (serial ghosters). Pending users with contact_count = 0 must have been in the pool more than 24 hours. Users on their second attempt must be 7+ days past their last expired match.
Explode each user into SessionSlots
A once-a-day user contributes one slot. A 'Twice a day' user contributes two. Local time is converted to UTC fresh on every run so DST changes do not produce stale UTC. This is the move that separates this matcher from every competitor.
for (const p of eligible) {
const morn = toUtcTime(p.morning_time, p.timezone);
if (morn !== null) slots.push({
personId: p.id, person: p,
session: "morning", utcMinutes: toMin(morn),
});
if (p.frequency === "Twice a day") {
const eve = toUtcTime(p.evening_time, p.timezone);
if (eve !== null) slots.push({
personId: p.id, person: p,
session: "evening", utcMinutes: toMin(eve),
});
}
}Generate viable pairs with the 60-minute UTC filter
For every pair of slots from different people, compute the wrap-around shortest distance on a 24-hour clock. If it is greater than 60 minutes, the pair is dropped. Pairs already in the blockedPairs set (any prior matched pair) are dropped too.
const diff = timeDiff(sa.utcMinutes, sb.utcMinutes);
if (diff > 60) continue;
if (blockedPairs.has([sa.personId, sb.personId].sort().join("|")))
continue;Sort by match quality
Ready-status pairs win. Ties broken by both-old-student, then same session duration, then the smallest UTC gap. The sort guarantees the best possible first match per slot.
allViable.sort((x, y) => (y.readyScore - x.readyScore) || (Number(y.bothOld) - Number(x.bothOld)) || (Number(y.sessionMatch) - Number(x.sessionMatch)) || (x.diff - y.diff), );
Greedily consume slots, keyed by (personId, session)
Walk the sorted list. For each pair, skip if either slot is already used. Used-slot tracking is per session, so a twice-a-day user holding a morning match can still receive an evening match in the same run.
const usedSlots = new Set<string>();
for (const p of allViable) {
const keyA = `${p.slotA.personId}:${p.slotA.session}`;
const keyB = `${p.slotB.personId}:${p.slotB.session}`;
if (usedSlots.has(keyA) || usedSlots.has(keyB)) continue;
pairs.push(p);
usedSlots.add(keyA);
usedSlots.add(keyB);
}Create the match, provision the Meet, send the email
If both people are 'ready', a Google Calendar event is created at the midpoint UTC time via createMeetEvent(), the permanent Meet URL is written twice into meet_links (one row per person with a unique tracking token), and both users receive the intro email. Reply-all is pinned in the subject because the email thread IS the app.
Two paths, side by side
The same desired outcome (a matched peer, a daily session, a sense that someone is waiting for you tomorrow) reached two different ways. Toggle below.
The app path
The app-first path
- Install the app from the store.
- Create an account with a password or OAuth.
- Walk through onboarding: goal, frequency, notifications, optional money on the line.
- Get matched in-app with a rotating partner (Focusmate-style) or a coach (Coach.me-style).
- Open the app each morning, accept the notification, tap to start, see your streak counter.
- Uninstall in 30 days because the app became another thing to open. (Median meditation-app retention at 30 days is roughly 4.7%.)
The no-app path
The no-app path
- Fill out one form on vipassana.cool/practice-buddy. Two minutes.
- Operator eyeballs the signup, drops it into the pool. 24-hour cooldown before auto-matching kicks in.
- Cron builds SessionSlots, applies the 60-minute UTC filter, sorts by match quality, pairs greedily.
- Google Calendar event is provisioned with a permanent Meet URL. Intro email lands for both people with Reply-All subject.
- Both reply, bookmark the Meet URL, open it at sit time every day.
- The link never changes. No account to remember. No streak to protect. The pair just exists.
Feature-by-feature vs. the category
A compressed view. “Typical app” is the category average (Focusmate, StickK, Beeminder, Yoke, HabitShare, Coach.me). Practice Buddy is the column on the right. The asymmetry below is the shape of the gap.
Why a meditator wants zero app, specifically
The standard research line in this category is that a specific accountability appointment with a named person pushes goal completion toward 95%, versus roughly 10% for a goal held in your own head. That part is well established across a meta-analysis of 42 habit studies. The less-discussed part is the delivery mechanism. The same research shows that when the accountability is delivered through a notification, the effect is large on day one, smaller on day fourteen, and mostly gone by day thirty. Retention decays with the app.
When the accountability is delivered through a person who is actually waiting, the effect does not decay that way. The reason meditation specifically benefits from the person-present model is that the hardest minute of meditation is the one before you start, and the one thing a push notification cannot do is be a second consciousness sitting in a Meet room at 5:59 AM expecting to see you.
The no-app approach is not a branding choice. It is the result of asking which layer of the stack produces the behavior change. In this category, the layer is the human, not the app.
Who this is and is not for
- Is for: old students of S.N. Goenka (or committed non-old-students) who want a peer to sit with over Google Meet, at a recurring daily time, for the foreseeable future. Matching prioritizes old students together.
- Is for: sitters who want a different morning and evening buddy. The session-slot matcher is built for this case specifically.
- Is not for: people who want a coach, a teacher, or guided content during the sit. There is no content layer on Practice Buddy. You bring your own Goenka recording and share the audio through Google Meet.
- Is not for: people who want a streak counter, a leaderboard, or public accountability. The site does not surface either. There is no dashboard. The only visible stat is a count of matched pairs on the homepage.
- Is not for: non-meditation accountability (workouts, study, language practice). The architecture generalizes but the product does not. If that is what you need, Focusmate is probably a better fit.
My verdict, after 900+ consecutive days
I built this because I was already doing it. For over three years I have opened the same Google Meet link every morning with the same practice partner. One of us plays the Goenka chanting, the other watches. We sit for an hour. We talk for five minutes or we do not. We go start the day.
On the days I did not feel like sitting, which is many days, the single mechanism that got me on the cushion was not a streak, not a notification, not a coach, not an app. It was knowing the other person would be in the Meet room at 6 AM whether I showed up or not. That is not willpower. That is not a habit loop. That is another human waiting.
Every accountability partner app I looked at before building this tried to simulate that feeling with software. The cheaper, simpler answer is to just match the humans and hand them a link.
Frequently asked questions
What is an accountability partner app, and why would I skip the app part?
An accountability partner app is a product that pairs you with another person so you can hold each other to a commitment (workouts, study, meditation, startup work). The classic ones (StickK, Beeminder, Focusmate, Yoke, HabitShare, Coach.me, MyFitnessPal buddies) all ship a mobile or web app with streak counters, push notifications, in-app chat, and sometimes money on the line. The reason to skip the app part is specific to the kind of accountability you need. If the thing you are trying to do every day is already screen-based (language practice, reading, startup work), in-app features help. If the thing you are trying to do every day is a 60-minute silent meditation, another app icon on your phone is drag, not support. The product on this page, Practice Buddy, is designed for the second case: a pair of meditators, a permanent Google Meet link, an email, and nothing else to install.
What exactly does Practice Buddy do if it is not an app?
One signup form on the website. A human review (the operator, me). A match based on a 60-minute UTC window applied at the session level, not the person level. An intro email delivered with both of your names, cities, practice details, a suggested meeting time in both local timezones, and a permanent Google Meet URL. You open that URL at your sit time every day, one of you plays the Goenka recording and shares screen plus audio, and you meditate together. After the sit you talk or you do not. The link does not change. There is no app, no dashboard, no streak counter, no push notification. The only thing the site tracks is whether the tracked /meet/<token> redirect was clicked, which is how a human (again, me) can tell if a match is active.
What is the 60-minute UTC filter and why is it a hard limit?
The matching cron, in /src/app/api/auto-match/route.ts, builds a SessionSlot array where each person contributes one slot per sit (a morning slot for once-a-day, two slots if they signed up for twice a day). Every slot carries a UTC minute count derived from the person's local sit time and timezone, recomputed fresh each run so daylight-savings transitions do not corrupt matches. The pairwise check is literally 'if (diff > 60) continue;' where diff is the shorter of the two distances around a 1440-minute clock. The reason it is a hard limit and not a preference is that meditation accountability requires overlapping presence. A 90-minute gap means one person is always waiting in an empty room while the other's session runs past. Sixty minutes is roughly the longest gap where 'meet in the middle' still produces two sits that actually happen.
Why match per session instead of per person?
Because a serious meditation practitioner often sits twice a day (morning plus evening), and the morning buddy does not need to be the evening buddy. In the code, a signup with frequency 'Twice a day' produces two slots: { personId, session: 'morning', utcMinutes } and { personId, session: 'evening', utcMinutes }. The greedy pairing loop consumes slots by the key `${personId}:${session}`, which means one user can end up with two different buddies at two different UTC windows. Every other accountability partner product treats a user as a single matchable unit. That forces a single compromise time. Treating the session as the unit lets you match a morning sitter in Berlin with a morning sitter in Delhi, and the same Berlin user's evening slot with a US East Coast user's morning slot, at the same time, without contradiction.
Is Practice Buddy free and how does it stay free?
Yes, free. It follows the Vipassana dana tradition, the same economic model that runs every Goenka 10-day course. The operating cost is low for exactly the reason the product is built this way: no app store presence, no push notification vendor, no in-app chat provider, no real-time infrastructure. Email is cheap (one Resend workspace covers it), Google Meet is free for 1-on-1 calls with no time cap, and Neon Postgres for the waitlist and matches table costs almost nothing at this scale. The only variable cost is the operator's time, and that time is spent on reviewing signups and writing intro emails, not on shipping app features. Donations are welcome but not solicited.
How does it compare to Focusmate, StickK, Beeminder, Yoke, or Coach.me?
Focusmate is closest in shape (1-on-1 peer sessions over video) but is built around 25/50/75 minute focused-work blocks booked on a shared calendar, not around daily meditation with a permanent link to the same partner. StickK and Beeminder use money-on-the-line with an optional referee, not a matched partner. Yoke is a workout-specific pair product with an app-first UX. Coach.me pairs you with a paid coach, not a peer. HabitShare is a streak-shared app between existing friends. Practice Buddy overlaps none of them exactly: it is peer-paired like Focusmate, recurring with the same partner like a gym buddy, and completely app-less like an email list. The trade-off is that it only serves meditators and specifically serves old students in the Goenka lineage (signup asks for course completion status and matches prioritize old-student pairs).
What stops bots, ghosters, or people who signed up on a whim?
Three mechanisms, all in the cron. First, a 24-hour cooldown: a pending user is not eligible for auto-matching until they have been in the pool for more than a day, which is enough time for the operator to eyeball the signup and remove anything that looks automated. Second, a serial-ghoster cap: if a person has contact_count greater than or equal to 2 (meaning they have already ignored two confirmation emails), the cron silently excludes them from all future matches. They are not notified and not punished, they are just out of the pool. Third, a prior-pair block: a set of person pairs rebuilt each run prevents two people who have ever matched before (whether it worked or not) from being offered to each other a second time. The combination keeps the pool's signal-to-noise high without any algorithmic spam filter.
What exactly does the permanent Google Meet URL give me that a normal accountability app does not?
A single URL that opens the same video room every day, forever. The URL is created through a server-side call to createMeetEvent (Google Workspace API) at the moment a match is confirmed, and it is written once into the meet_links table. Each person in the pair gets a per-user tracking token that redirects to the same shared URL through /meet/<token>, which logs a row into meet_clicks so the operator can tell whether your buddy opened the link this morning. You do not see that data in a dashboard (there is no dashboard) and you are not ranked on it. The benefit of the permanent URL is friction removal: you bookmark it once, you add it to your home screen as a web shortcut, and from that point forward the path from waking up to sitting with your buddy is two taps.
Why do accountability partner apps have such bad retention, and does this approach actually fix it?
Meditation apps specifically have roughly a 4.7% 30-day retention rate (widely cited figure across the well-being category). The reason is that the thing being measured (daily meditation) is not improved by the thing the app is actually good at (delivering content, sending notifications, awarding streaks). Meta-analyses on habit formation consistently find that a specific accountability appointment with a named person pushes completion rates toward 95% versus around 10% when the goal is solo. The practical question is what delivery mechanism costs the least friction. An app asks you to open the app, see the notification, feel the streak, tap start. A Practice Buddy partnership asks you to join a link where a human is already waiting. That second thing is much harder to bail on at 5:58 AM, and the data on peer accountability backs it up.
Is this approach generalizable outside meditation, or does it only work for Vipassana?
The specific build (permanent Google Meet URL, dana funding model, old-student priority, 60-minute UTC session slots) is tuned for Goenka-lineage meditators. The pattern generalizes. If you want daily peer accountability for any activity that does not benefit from screen-based content during the activity itself (morning runs, language conversation practice, writing sprints, journaling, cold exposure), the no-app approach works the same way. You need a form, a matching rule, a permanent video or audio room, and a human who cares enough to review signups. The product you do not need is another mobile app. The site you are reading this on treats Vipassana as the flagship case because that is the author's practice, but nothing in the architecture is meditation-specific except the question 'are you an old student of S.N. Goenka?' on the form.
Want the no-app version in your inbox?
One form, a human review, a permanent Google Meet URL with a matched meditator inside your 60-minute UTC window. Free.
Find a Practice Buddy