Slide home
patcher.com

Cofounder · Live on the Jobber marketplace

Patcher

When a customer cancels, the empty slot costs a service business real money. Patcher watches the calendar and fills the gap before it becomes lost revenue.

Live in productionpatcher — live
Patcher landing page: “Turn cancellations into filled appointments.”
patcher.com · the public landing page

01 · The elevator pitch

What it is

When a customer cancels on a service business, the empty slot costs more than one visit: the tech still drives around it and the day still gets paid for. Patcher plugs into the scheduling tools these businesses already use, spots the gap the moment it opens, finds which jobs can safely move up, and either texts the best fit itself or drafts the proposal for a one-tap Send.

I'm the cofounder and the engineer: the scheduling engine, the integration layer, the API, and the frontend are all mine, end to end. It's live, approved on the Jobber marketplace.

02 · The core

The scheduling engine

Moving a job into a gap changes the drive before and after the slot, the slack left in the day, and which customers you're comfortable nudging. So the engine spends the expensive math only on candidates that earn it: cheap in-memory filters cut the field, a Haversine travel proxy pre-ranks the survivors, and only the top of that queue gets real Google Distance Matrix calls. The proxy runs the same detour and slack math as the verified pass, just on approximate inputs, so the two can never disagree.

Candidates aren't crowned by a score anymore. Each one passes hard yes-or-no checks: does the job physically fit, is the added drive inside the business's limit, can the customer be reached, is a technician swap allowed. Managers see the facts they'd check themselves, plus a plain-English reason when a candidate isn't ready. A fit score still exists under the hood; it just no longer decides who gets texted. And while a dispatcher reads one gap, a background worker pre-builds the next few, saving roughly 800ms per click.

Candidate pipelineIllustrative run

Gap detected · Tue 9:40 AM · 2h 30m open

Candidates142
Cheap filters38
Pre-ranked38
Verified10
#1Maria RiddleFurnace inspection · 75 Auburn St+9m drive

Proposal ready · May 27 May 22 · 5 days earlier

03 · The connections

The integration layer

Patcher meets each platform where it lives. Three scheduling adapters (Jobber over OAuth, MoeGo and Housecall Pro over API keys) sit behind one interface, so the engine never knows whose calendar it's reading. Google, Twilio, and Slack are utilities a layer down: calendar and maps, SMS delivery, notifications.

The adapters are honest about platform differences. Moving an appointment is one atomic call on Housecall Pro, two separate mutations on Jobber, and on MoeGo, which has no reassign endpoint, a create-then-cancel sequence in that order so a failed create leaves the original untouched. Writebacks are treated as proposals, not facts: every adapter parses what the provider actually did, and a verifier confirms the move really happened before Patcher believes it. Each adapter also ships a fixture twin so the core flows run deterministically in tests.

04 · The plumbing

Async infrastructure

Webhooks arrive badly: in bursts, out of order, more than once. They get their own front door, a second small FastAPI app that validates the signature, writes a row, and returns 200 in milliseconds, so a webhook storm can never starve real users. Payloads are hashed for dedupe and the downstream writes are idempotent, so a duplicate delivery collapses into a no-op.

Five gap-detection triggers used to race each other. Now every trigger feeds one durable queue in Postgres (no Redis, no Celery): workers claim withFOR UPDATE SKIP LOCKED, priorities are integers the SQL sorts on, and pending requests for the same integration merge at claim time. A per-integration lease, one conditional UPDATE with a five-minute TTL, guarantees two detection runs can never overlap.

Webhook ingestIllustrative run
visit.updated · jobber · a41f9cqueued
visit.updated · jobber · a41f9chash match · dropped
visit.cancelled · jobber · 07c2e1queued
calendar.updated · google · d9e844queued
Distributed lease held: runs never overlap3 events 1 gap-detection run · lease held

05 · The last mile

The proposal flow

Filling the gap runs in one of two gears. On autopilot, the engine sends the proposal itself the moment a gap clears its checks. In approve-first, it does the same work and stops one tap short: the drafted proposal waits in a tray until a human hits Send. Pacing is decided by urgency: an opening starting soon goes to every eligible candidate at once, and a farther-out one is offered one candidate at a time, auto-advancing on declines.

The SMS is deliberately dumb: replies never change proposal state, and accepting happens only through a tokenized link, never by parsing "YES". Guardrails wrap the rest: a consent disclosure fires exactly once per customer, quiet hours are quiet, someone just served isn't re-contacted, and a decline pauses that customer until after their next appointment. A separate reschedule link lets a customer browse eligible slots and pick their own time, powered by the same travel engine.

06 · War stories

Production stories

Resolved in production

The webhook outage

Production debugging

Under burst webhook load, the API wedged. The sync database client held one HTTP/2 connection for the life of the process; when the pooler recycled its backend, the client never noticed the dead socket, and every request failed until a restart. The fix: hot paths moved to an async client, and the worst endpoints (one made 30-plus sequential round trips) moved to hand-written SQL and single JOINs.

Resolved in production

The page that made 300 API calls

Production debugging

The customer reschedule page checked travel feasibility for every visible gap, so one page load could fan out to 300-plus Distance Matrix calls and stall for 30 seconds. The fix was honesty about limits: cap the sweep, narrow the window, and hand the customer a page of solid options fast.

07 · The product

Screenshots

Real product screenshots are on deck. Until they land, the live thing is at patcher.com ↗.

product ui · 01
Screenshot placeholder
product ui · 02
Screenshot placeholder

08 · The toolbox

Stack

Frontend
React, TypeScript, Vite, Tailwind
API
Python 3.12, FastAPI, Pydantic
Data
PostgreSQL on Supabase (PostgREST, direct asyncpg on hot paths)
Messaging
Twilio SMS, A2P-registered
Infra
Docker, Render, Vercel, GitHub Actions

Three services run in production: the API, a webhook app, and a background worker. The schema moves fast on rails: 251 migrations in eight months, each CI-gated and deployed automatically on merge.

Postgame

See it live

Patcher is in production today, filling real schedules for real businesses.