The idea
A one-page site for a single recurring group — the Tuesday run club, the monthly repair café, the Thursday board game night. The organiser defines the recurrence once ("every Tuesday 18:30, meet at the north gate"), then does nothing most weeks. When something changes they send one message to a Telegram bot: "cancel tonight", "moved to the café", "no run next week, holiday". The page updates, subscribers get a push, and an ICS feed they added to their phone calendar months ago reflects the exception. The whole product is one URL you can paint on a sign.
Why build this
Small recurring groups are run by one slightly tired volunteer, and the thing they spend their energy on is not organising the event — it is answering "is it still on tonight?" six times by text. The event itself is stable. The exceptions are the work.
Every tool available to them handles this backwards. A Facebook or Instagram post is a new post per occurrence, invisible to anyone not scrolling at the right moment, and a cancellation is a comment nobody reads. A WhatsApp group reaches the regulars and nobody else, and newcomers can't find it. Meetup charges a monthly subscription for a group with eleven people in it and no money. A shared Google Calendar works technically but requires every attendee to have a Google account and the organiser to understand calendar sharing permissions, which is a losing bet. So most groups end up with a stale website and a phone number.
The timing argument is boring and real: RFC 5545 recurrence rules have been universally supported on phones for a decade, every mainstream calendar app subscribes to a remote ICS URL, and the library work for both is a solved npm install. The product is not a calendar engine. It is the three lines of UX that let someone who has never heard the word "RRULE" cancel one occurrence without breaking the other fifty.
Stack sketch
- App: SvelteKit on Node, server-rendered. The public page must be fast on a phone with two bars of signal outside in the dark, so it ships as mostly static HTML with no client framework on the critical path.
- Recurrence: the
rrulepackage for expansion, storing an RFC 5545RRULEstring plus a separate table of per-occurrence overrides (cancelled, retimed, relocated, note). Never materialise occurrences into rows — expand on read, apply overrides, cache the next five. - Calendar feed: the
icspackage, served at/g/<slug>.icswithX-WR-CALNAMEset and cancelled dates emitted asEXDATE, not as deleted events. Relocated occurrences becomeRECURRENCE-IDoverrides so clients update in place rather than showing a duplicate. - Organiser interface: a Telegram bot via
grammy, because the organiser is already in Telegram and will not log into an admin panel at 17:50. Free-text messages go to Claude Haiku with a tool schema (cancel_occurrence,change_location,post_note) and the bot replies with the parsed intent for a one-tap confirm — never apply an edit without confirmation. - Notifications: Web Push via
web-pushfor people who tapped "notify me", plus the ICS feed for everyone else. No email, no phone numbers, no accounts. - Storage: Postgres. Groups, overrides, push subscriptions. The whole schema is four tables.
- Hosting: one small VPS behind Traefik, with the public page and the ICS feed cached at the edge for sixty seconds.
Scope for v1
- One group per link, created from a short form, with an unguessable admin link as the only credential. No signup.
- Weekly and monthly recurrence, a time, a place as free text plus an optional map link.
- Three exception types: cancel this one, move this one, add a note to this one.
- Public page showing the next occurrence large, the following two small, and any active note.
- ICS subscription URL and a Web Push opt-in.
- Deliberately out: RSVPs, attendee lists, capacity, payments, multiple organisers, photos, discussion, a directory of groups, and anything resembling a social graph.
Where it could go
The first expansion is capacity, because a meaningful subset of these groups are capacity-limited — a workshop with eight sewing machines, a pitch booked for fourteen players. That needs RSVPs and a waitlist, and it is the point where the product stops being free to run and starts being worth a few pounds a month to the organiser. Keep it opt-in per group; adding RSVPs to the run club would make it worse.
The second is aggregation, and it falls out of the architecture rather than being built. Once a few hundred groups in a city publish machine-readable feeds, "what is on near me this week" is a query over data you already hold, and that is a genuinely better local events listing than anything scraped. The third, smaller path is embedding: a single <script> that renders the next-occurrence block inside a club's existing WordPress site, so the group doesn't have to choose between the two.
Watch out for
Calendar clients refresh subscribed feeds on their own schedule — often every few hours, sometimes once a day, and Apple's interval is not something you control. A cancellation sent ninety minutes before the event will reach the public page instantly and a subscriber's phone possibly never, so treat the feed as the plan and push as the alert, and say so on the page rather than letting someone stand in the rain trusting their calendar.