Integration PRD · v1.0
Integration PRD
1 · Google Calendar (v1 — the only calendar)
| Capability | Scope | Detail |
|---|---|---|
| Conflict nudges (read) | calendar.readonly | Organizer-only in v1. Free/busy per painted cell → "clashes with your 1:1" nudge. Invitee OAuth deferred to v1.1 — guests get no nudges, by decision. |
| Event write | calendar.events | On lock: event on organizer's calendar with all invitees as guests (Google emails them natively). Reschedule updates the same event; history in description. |
| Decline detection | push channel (events.watch) | Watch registered per locked meeting → /api/webhooks/gcal → DESTABILIZED → 1e. Channels renew via scheduled function (they expire ~7 days). |
- Incremental consent: read scope requested at create-time only if organizer opts into nudges; write scope requested at first lock.
- Failure mode: if OAuth is declined, lock still succeeds — confirmation falls back to Resend email with slot details, and decline detection falls back to in-app decline only.
2 · Resend (email — the only v1 notification channel)
| Template | Trigger | Content rule |
|---|---|---|
invite | meeting created | First-person ask + tokenized paint link. No account language. |
reveal | state → PROPOSED | To organizer: the one proposal + ⓘ breakdown summary. |
lock_confirm | state → LOCKED | To all: slot, attendees, quorum status. Calendar invite arrives separately from Google. |
flex_ask | 2f flex sent | To one invitee: one-tap Sure / Can't links. Reveals aggregate only. |
resched | re-lock after 1e | Old slot struck through, new slot, one-line reason. |
magic_link | guest claims history | 15-min single-use token → Supabase auth session. |
- All templates versioned in-repo (React Email); same no-LLM rule as product copy. Domain auth (SPF/DKIM) on the product domain before launch — owner: infra eng, wk 2.
3 · Netlify (hosting + functions)
- This PRD site:
wenmeet-prd.netlify.app(static, this folder, drag-and-drop deploy). Product: separate sitewenmeet-ccb. - Product build: static assets +
netlify/functions/*; env vars per context (Supabase URL/keys, Google client, Resend key); deploy previews get the staging Supabase project. - Scheduled functions: Google watch-channel renewal (daily), retention anonymizer (daily).
4 · v2 media layer — deferred, decision wk 6
Decision date: end of week 6 (v1 launch), from the wk 4–6 spike readout. Owner: infra eng. The spike prototypes join-by-drag latency inside the live graph for both SDK options.
| Option | For | Against |
|---|---|---|
| Daily (SDK) | Fastest embed; prebuilt UI escape hatch; recording API for 2e async recaps. | Per-minute cost scales with blob dwell time; less layout control inside the blob ring. |
| LiveKit Cloud | Deep UI control (tiles inside the blob), open-source exit ramp, good data channels for presence. | More integration work; recording pipeline is ours to assemble. |
| Meet/Zoom links | Near-zero build; blob is just a door. | Breaks the core v2 promise — join-by-drag becomes join-by-new-tab; no presence inside the blob; ruled out unless spike fails both SDKs. |
- Decision criteria, in order: (1) drag-in → media-connected < 2s, (2) presence fidelity (who's inside, live, from the graph), (3) recording for the 2e async contract, (4) cost per active blob-hour.
- Whatever wins, the blob contract is fixed by the v1 groundwork: solid ring = enterable, × closes and folds artifacts back, membership changes go through
graph_nodes.
5 · Explicitly not integrated in v1
- Outlook / CalDAV / ICS export — Google-only by decision; revisit with demand data post-launch.
- Slack, push, SMS — email is the only channel; the orb is the in-app channel.
- Any LLM API — the AI is the deterministic scorer + motion; there is nothing to call.