Farewell Wishes
System Design Proposal
Farewill Engineering — System Design Review
Agenda
Today's session covers the four things promised in the design document:
- Technology choices — what we're using and why
- Build vs. modify — which existing systems are touched
- Communication protocols — how components talk to each other
- Database adaptations — what changes in MongoDB
Starting Point: Existing Infrastructure
Before any decisions, here's what Farewill already has:
| Layer | What exists |
|---|---|
| Frontend | React SPAs on Cloudflare CDN (Will Writing, Account Mgmt) |
| Backend | Node.js Will Writing API on AWS |
| Database | MongoDB with users, wills, gifts, people, possessions |
| Auth | Account Management API issuing JWTs |
All four of our decisions are anchored to this stack.
1. Technology Choices
| Component | Choice | Status |
|---|---|---|
| Frontend | React SPA on Cloudflare | 🆕 New |
| Backend | Will Writing API (Node.js) | ✏️ Extended |
| Database | MongoDB (existing DB) | ✏️ Extended |
| File storage | AWS S3 | 🆕 New bucket |
Guiding principle: align with the existing stack. No new languages, no new cloud providers.
Why These Choices
React SPA on Cloudflare — matches the existing frontend paradigm; reuses component libraries for design consistency; Cloudflare handles 0.016 avg RPS in Year 1 trivially.
Extend the Will Writing API, not a new microservice — peak load is ~0.3 RPS in Year 1, ~200 RPS in Year 5. A new service adds maintenance overhead without benefit. Shared codebase also makes the conversion-to-will flow straightforward.
Reuse MongoDB — existing collections already model users, people, possessions and gifts. 20 MB structured data in Year 1, 12.5 GB by Year 5 — well within standard MongoDB capacity.
New S3 bucket — photo uploads aren't in the current architecture. At 40 GB Year 1 → 25 TB Year 5, dedicated object storage is required. S3 is the natural choice within AWS.
2. What Gets Built vs. Modified
| System | Action | Detail |
|---|---|---|
| Wishes React SPA | Build new | New Cloudflare-hosted SPA |
| Will Writing API | Extend | Add /v1/wishes/* endpoint namespace |
| MongoDB | Extend | Add four new wish_* draft collections |
| AWS S3 | New bucket | Photo storage with lifecycle policy |
| Account Mgmt API | No change | Consumed as-is for JWT auth |
| Will Writing SPA | No change | Receives user at conversion via redirect |
| Funerals/Probate | No change | Entirely separate system — out of scope |
One new service. Two extensions. Everything else untouched.

3. Communication Protocols — API Contract
New endpoints namespaced under /v1/wishes/ — isolated from existing will-writing routes.
| Action | Method | Endpoint |
|---|---|---|
| Fetch session state | GET |
/v1/wishes/session |
| Upsert loved one | PUT |
/v1/wishes/people/:id |
| Upsert possession | PUT |
/v1/wishes/possessions/:id |
| Upsert gift / message | PUT |
/v1/wishes/gifts/:id |
| Request upload URL | POST |
/v1/wishes/upload-url |
| Trigger conversion | POST |
/v1/wishes/convert |
All PUT endpoints are idempotent — safe to call repeatedly on autosave.
All errors return a machine-readable error.code field.
3. Communication Protocols — Authentication
Problem: forcing sign-up at entry kills conversion.
Solution: deferred authentication
User starts journey → anonymous session in localStorage
↓
User reaches first save point
↓
Auth prompt (Account Management API → JWT)
↓
Server merges anonymous session into authenticated account
Token handling: short-lived JWTs (15 min) + silent refresh via HttpOnly cookie. The Will Writing API validates tokens locally using the shared public key — no round-trip to Account Management per request.
3. Communication Protocols — Image Upload
Images bypass the Node.js API to avoid blocking its event loop.
1. SPA → API POST /v1/wishes/upload-url
{ contentType, fileSizeBytes }
2. API validates size ≤ 5 MB, MIME type = image/*
API → SPA short-lived presigned S3 PUT URL (5 min TTL)
3. SPA → S3 PUT photo directly (HTTPS)
4. SPA → API PUT /v1/wishes/people/:id { photoUrl: "s3://..." }
Security note: client-side compression is a bandwidth optimisation only. The 5 MB limit is enforced server-side in step 2, and S3's content-length-range condition prevents bypass via direct upload.
3. Communication Protocols — Conversion Flow
When a user clicks "Create my legal will":
SPA → POST /v1/wishes/convert
↓
API atomically promotes wish_* draft data
into the canonical will schema
↓
Returns redirect URL to Will Writing SPA
(pre-populated with user's data)
This is a synchronous, server-side operation — no event bus needed at current scale.
If the architecture evolves toward microservices,
POST /v1/wishes/convertis the natural integration point for an async SNS/SQS event.
4. Database Adaptations — The Decision
Option A: reuse existing collections (add source: "wishes" flag)
- ✅ No migration
- ❌ Wish data is partial — will fail existing will schema validation
- ❌ Pollutes production collections; harder to clean up abandoned sessions
Option B: new wish_* draft collections ← recommended
- ✅ Clean separation; no risk to existing data integrity
- ✅ Independent lifecycle (retention, deletion, GDPR)
- ✅ Single explicit boundary at conversion time
- ❌ Requires a promotion step at
POST /v1/wishes/convert
POST /v1/wishes/convert is the one controlled point where partial draft data is validated and promoted into the canonical schema.
4. Database Adaptations — New Collections
Four new collections, all prefixed wish_:
| Collection | Purpose | Key fields |
|---|---|---|
wish_sessions |
Top-level progress tracker | userId, status |
wish_people |
Loved ones (Q1) | userId, name, photoUrl |
wish_possessions |
Possessions (Q2) | userId, name, description, photoUrl |
wish_gifts |
Gift/message assignments (Q3) | userId, recipientId, possessionId, message |
status on wish_sessions: in_progress → completed → converted
Storage impact: ~20 MB in Year 1, ~12.5 GB by Year 5 — negligible within existing MongoDB deployment.
4. Database Adaptations — Indexes & Lifecycle
Indexes — every collection indexed on userId (the primary lookup key):
| Collection | Index |
|---|---|
wish_sessions |
{ userId: 1 }, { userId: 1, status: 1 } |
wish_people |
{ userId: 1 } |
wish_possessions |
{ userId: 1 } |
wish_gifts |
{ userId: 1 } |
Data lifecycle:
- Active sessions: retained indefinitely
- Converted sessions: retained 12 months post-conversion, then soft-deleted
- Abandoned sessions (no update in 24 months): flagged for deletion — GDPR data minimisation
- Right to erasure: all
wish_*collections covered by the existing user-deletion workflow
5. Security Controls
| Control | What it enforces |
|---|---|
| Authorisation | Every endpoint asserts JWT userId matches the resource — no cross-user access |
| Data in transit | HTTPS everywhere; Cloudflare redirects HTTP; S3 rejects HTTP PUT |
| Data at rest | MongoDB EBS encryption (confirm active); S3 SSE-S3/KMS on photos bucket |
| S3 bucket access | Private — no public ACL; photos served only via time-limited presigned GET URLs |
| Upload hardening | MIME type allowlist, 5 MB limit enforced server-side + S3 content-length-range |
| Rate limiting | 20 presigned URLs per user per hour on POST /v1/wishes/upload-url |
| Input validation | Max field lengths enforced at API (names: 100 chars, messages: 2,000 chars) |
| Secrets management | JWT keys + AWS credentials in AWS Secrets Manager — not in env vars |
GDPR controls (right to erasure, retention, data minimisation) are covered in the Database Adaptations section.
Summary
| Deliverable | Decision |
|---|---|
| Tech choices | React SPA + extend Will Writing API + MongoDB + S3 |
| Build vs. modify | 1 new service, 2 extensions, 4 systems untouched |
| Communication | REST /v1/wishes/*, deferred JWT auth, presigned S3 uploads, sync conversion |
| Database | 4 new wish_* draft collections; promoted at conversion |
| Security | Authorisation per request, HTTPS+SSE at rest, upload hardening, secrets in AWS SM |
Design principle throughout: prefer the simplest solution that meets the requirement and aligns with what Farewill already operates.
Questions?
Farewell Wishes
By iketari
Farewell Wishes
- 1