Feel free to use the contact form or other contact options.

A booking and practice-management platform for a Nairobi counsellor. Someone looking for help is rarely in the mood to create an account — so the whole flow is built around booking as a guest in four short steps, then paying with the phone already in their hand.
Behind the public site sits a private dashboard for the practitioner and a Cloudflare Worker API shared by both.
| Layer | Technology |
|---|---|
| Public site | Next.js — booking, services, articles |
| Dashboard | Next.js — schedule, clients, payments |
| API | tRPC on Cloudflare Workers |
| Database | D1 (SQLite) + Drizzle |
| Realtime | Durable Object per practice, WebSocket Hibernation |
| Payments | M-Pesa Daraja (STK push) |
| Messaging | Africa's Talking SMS, Resend email |
Choose a session type, pick a time, leave contact details, pay. Guests get a signed access link instead of a password, so they can manage the appointment later without ever registering.
M-Pesa STK push prompts the client's handset directly. Daraja callbacks arrive more than once and out of order, so payment state is keyed on the checkout request and applied idempotently — a duplicate callback is a no-op, not a double charge.
A scheduled job sweeps upcoming appointments and sends SMS and email reminders. Overlapping runs are explicitly covered by tests, because a reminder job that fires twice is a message the client reads twice.
Deep Slate
Warm Sand
Accent Coral
Paper
Two guests can hit the same 10:00 slot in the same second. A "is this taken?" check before writing is a race, not a guard — it just narrows the window.
The application still does that check, because it produces a friendly message. Correctness comes from a partial unique index on organisation, date and time, scoped to active statuses only. The database refuses the second booking outright, and because the constraint ignores cancelled rows, a freed slot becomes bookable again with no cleanup job.
M-Pesa's Daraja API delivers callbacks more than once, sometimes out of order, occasionally long after the client has closed the tab.
Payment state is therefore keyed on the checkout request rather than assembled from the sequence of callbacks, and every transition is idempotent — replaying a callback changes nothing. Duplicate callbacks and out-of-order state changes are both covered by integration tests running against an isolated D1 instance.
The practitioner's dashboard should show a new booking the moment it lands, without hammering the database on an interval.
Each practice gets its own Durable Object acting as a notification hub. Producers send a lightweight poke, connected dashboards refetch authoritatively. It uses the WebSocket Hibernation API, so idle connections stay open without holding the object in memory — you pay for events, not for how long someone leaves the tab open.
The platform is multi-tenant, and in a mental-health context a leak between tenants is not a bug you get to explain away.
Every table carries an organisation, every query is scoped to it, and tenant isolation is asserted in the integration suite alongside the payment and booking cases rather than left to code review.






