System architecture
Two frontends, one backend, one database — and three independent repos, not a workspace. Their deploy targets have nothing in common, and a shared lockfile would only couple them.
- Dashboard: The authenticated app — meetings, cards, tasks and the meeting-AI interface. Client-rendered, because nothing here is worth indexing and every view sits behind a login.
- Public pages: Card pages and booking links, on their own domain with no auth at all. Server-rendered because these are the only pages that have to look right in a search result or a link preview. Its own repo and its own deploy — the one thing keeping dashboard bundle weight off a page a stranger loads.
- calendar-backend: One service owning every route: auth, meetings, cards, the meeting-AI endpoints and the public read paths. Prisma talks to Postgres; nothing else does.
- Cloud Storage: Raw audio lands here first and the worker reads it back out. Keeping recordings out of the database is what lets a transcript row stay small enough to query cheaply.
- PostgreSQL: All 47 tables. The worker writes results back onto the same meeting row the API already served, which is the entire reason this is relational rather than document storage.
- Bull queue: The seam. Upload enqueues a job and returns immediately, so a 30–120 second transcription never sits inside an HTTP request. Bull carries retry counts and job state, so a failure is observable rather than silent. Bull needs a real TCP connection to Redis, not a REST client — which is also why the worker can't be serverless.
- Worker process: A separate always-on process. Pulls the job, fetches the audio, calls transcription then the model, and writes results back. Three attempts with exponential backoff before the meeting is marked FAILED.
- Deepgram: Speech to text with speaker diarization on. Whisper was the obvious default and can't tell you who said what — the one property you cannot add afterwards in post-processing.
- GPT-4o-mini: Turns the transcript into a summary, key points and action items. Structured extraction rather than reasoning, which is why the small model is the correct one — about 10× cheaper at the same usable quality.
The hard part: meeting AI that doesn't feel slow
Transcription takes 30 to 120 seconds. You cannot hold an HTTP connection open for that, and a spinner in front of it means nobody uses the feature twice — so none of it runs on the request path.
The price of going async is that slow and stuck start to look identical. So the state is written down rather than inferred:
Failure modes
Two queues with deliberately different retry budgets. Transcription retries 3 times with 5-second exponential backoff — if Deepgram is down, it's down, and burning attempts costs money without changing the outcome. The Recall.ai webhook retries 8 times at 30 seconds, because a bot's recording genuinely isn't ready yet and the right response is to wait longer, not to give up sooner.
| When | What happens | Recovery |
|---|---|---|
| Deepgram 5xx or timeout | Job retries, meeting holds at PROCESSING | 3 attempts, exponential from 5s → FAILED |
| Recording not ready at webhook | Job re-queued, no partial write | 8 attempts, exponential from 30s |
| Worker process dies mid-job | Bull redelivers on next worker boot | Job is idempotent — re-transcribes cleanly |
| LLM returns unusable output | Transcript still persists; summary null | Regenerate endpoint re-runs only that step |
Trade-offs
Postgres over MongoDB. Meetings have participants, recordings have transcripts, transcripts have segments. A document store would have meant denormalising every one of those and then keeping it in sync by hand.
Deepgram Nova-2 over Whisper. Whisper is the obvious default and has no speaker diarization. A transcript that can't tell you *who said what* is close to useless for summarisation, and it's the one property you cannot add afterwards in post-processing.
GPT-4o-mini over GPT-4o. Transcripts fit comfortably in the smaller context and the task is structured extraction rather than reasoning — roughly 10× cheaper at the same usable quality, with a one-string upgrade path if that stops being true.
Recall.ai over building the bot. A bot means OAuth apps, bot infrastructure and recording pipelines — none of which is the product. Recall streams to Deepgram under my own credentials, so the pipeline runs unchanged.
Google OAuth as the only login. Solo professionals all have Google accounts, and Calendar sync needs the OAuth grant anyway. No password storage, no reset flow, no credential-stuffing surface — the cheapest security posture is the one with nothing to steal.
Where it stands
Deployed, with subscription billing wired end to end. Ask AI streams over SSE — a first token in a few hundred milliseconds reads as faster than a complete answer four seconds later, even though it finishes at the same time.