Settle — Prerequisites & Decision Register
Every infrastructure decision, with options and trade-offs, before any architecture document is written.
Read this, set up your accounts, and run docker compose up — all in under 30 minutes.
1. Docker image that runs anywhere — over platform-specific configuration.
2. "Can Postgres do this?" — before adding any new service.
3. Library-based auth — over managed auth services.
4. Every vendor has a documented exit path — before it is adopted.
5. MVP dependency budget: maximum 5 external services — 6 if counting Lob for physical mail.
Minimum Viable Infrastructure
Settle has more infrastructure surface area than a typical SaaS application. Three capabilities force decisions that most apps defer indefinitely: column-level encryption for PII (SSNs, death certificates), physical mail dispatch for Tier 2 notifications, and a state-specific rules engine covering 50 US jurisdictions. This section strips each down to its minimal viable form before any vendor is added.
A container host running the application and a managed Postgres instance with pgcrypto enabled.
That is the complete infrastructure core. Everything else is a feature decision, not an infrastructure
requirement. The sections below argue for deferring each additional service until user feedback
makes the deferral painful.
- Container hostFly.io, Railway, or Render
- Managed Postgrespgcrypto extension enabled
- Transactional emailResend — task reminders & auth
- Error trackingSentry free tier
- Lob / physical mailUntil Tier 2 ships
- Object storageUntil >5,000 docs
- Benefit scanner APIsUntil manual checklist is painful
- RedisUntil pg-boss is insufficient
Questioning Each Potential Service
The following table forces a deferral decision for every service that is commonly added "just in case." The decision is recorded here so it does not have to be re-litigated during architecture review.
| Service | Common assumption | MVP reality | Defer trigger |
|---|---|---|---|
| Lob (physical mail) | Required for Tier 2 notifications | Generate a formatted PDF. Executor prints and mails it. Zero API cost. | User feedback: print-and-mail is causing abandonment |
| Object storage (R2/S3) | Needed for document uploads | 500 estates × 5 docs = 2,500 docs. Postgres large objects handle this comfortably. | >5,000 documents or >5 GB in Postgres |
| Benefit scanner APIs | Core feature: automated discovery | Manual checklist: "Check these 5 sites for unclaimed property." Humans are fine at MVP scale. | Manual checklist creates measurable support load |
| Redis | Needed for sessions & queues | Sessions in Postgres. Job queue via pg-boss. Redis adds zero value before high concurrency. | pg-boss throughput becomes a bottleneck (>1,000 jobs/min) |
| Dedicated rules engine | 50 states requires Drools or json-rules-engine | State rules are if/then thresholds and form names. JSONB rows in Postgres are queryable, versionable, and require zero new dependencies. | Rule complexity exceeds what can be expressed in structured JSON |
Tier 1 (subscription cancellation calls) requires an email or phone integration. Consider whether Tier 1 is MVP scope at all. Starting with Tier 3 only — generate a script, the executor makes the call — defers all outbound communication infrastructure except task-reminder email. Tier 1 can ship in Month 2.
Decision Register
Each decision below includes the options considered, a comparison of trade-offs, and a recorded recommendation. Decisions marked Decided are final for MVP. Decisions marked Deferred are explicitly not being made yet. Revisit deferred decisions when their stated trigger is reached.
Settle requires a background worker process for notification scheduling alongside the web process. The hosting platform must run Docker images and support multiple process types without a significant pricing step-change at MVP scale.
| Option | Workers | Postgres included | Free tier | Migration effort |
|---|---|---|---|---|
| Fly.io Rec | Separate Machines, scale-to-zero | Yes (Fly Postgres) | 3 shared VMs | Change CI/CD target. ~1 day. |
| Railway | Separate services per process | Yes (Railway Postgres) | $5 credit/mo | Change CI/CD target. ~1 day. |
| Render | Background Workers service type | Yes (Render Postgres) | Free web service (spins down) | Change CI/CD target. ~1 day. |
| Cloud Run (GCP) | Cloud Tasks / separate service | No (Cloud SQL separate) | Generous free tier | GCP-specific config. ~1 week. |
Postgres with the pgcrypto extension is non-negotiable. Column-level encryption for SSNs and
sensitive PII must live in the application layer, not a third-party vault, at MVP budget. The only open
question is which managed Postgres provider to use.
Postgres + pgcrypto is the encryption strategy. Any managed Postgres provider supports extensions, including pgcrypto. Do not choose a provider that restricts extensions (e.g., some PlanetScale configurations). Verify pgcrypto is available before provisioning.
| Provider | pgcrypto | Long-lived connections | Notes |
|---|---|---|---|
| Fly Postgres Rec | Yes | Yes (not serverless) | Co-located with app; included in Fly account |
| Railway Postgres | Yes | Yes | Reasonable if using Railway for hosting |
| Neon | Yes | Serverless (cold starts) | Generous free tier; cold starts matter for background workers |
| Supabase Postgres | Yes | Yes | Row-level security is bonus; pauses on free tier |
Settle's user base is primarily grieving family members, many of whom are not technically sophisticated and may be elderly. Authentication must support email/password as the primary flow, magic links for users who lose passwords, and an invite-based access model so executors can grant view-only access to other family members.
| Option | Type | Magic link | Invite flow | Vendor lock-in |
|---|---|---|---|---|
| BetterAuth Rec | Library | Yes | Yes (organizations plugin) | None — Postgres-backed |
| Lucia | Library | Manual implementation | Manual implementation | None |
| Auth.js (NextAuth) | Library | Yes (email provider) | No built-in | None; Next.js-centric DX |
| Clerk | Managed service | Yes | Yes (Organizations) | High — data lives in Clerk |
SvelteKit has been selected. Brief rationale for the record:
- Server-side rendering and API routes in a single project eliminates a separate API server at MVP scale.
- Form actions provide progressive enhancement critical for a user base that may be on slow mobile connections.
- Bundle sizes are smaller than React-based alternatives — relevant for older devices commonly used by the target demographic.
- Compiler-based reactivity avoids the cognitive overhead of hooks for a solo or two-person team.
If a native mobile app is a Year 2 requirement, this decision should be revisited. SvelteKit does not share code with React Native or Flutter. A mobile app would be a separate project, or the frontend choice changes to React (web + React Native code sharing). See Open Questions, item Q-04.
This is an application decision, not an infrastructure decision. The 50-state rules do not require a dedicated rules engine service. They are structured data: thresholds (probate limits), form identifiers, required notices, and timelines. All of this is expressible as JSONB rows in Postgres.
| Option | Storage | Updates | Queryable | New dependency |
|---|---|---|---|---|
| JSONB in Postgres Rec | Versioned rows per state | SQL UPDATE or migration | Yes — full SQL access | None |
| YAML/JSON files in repo | Git-versioned files | Deploy on every change | No — application-level only | None |
| Dedicated rules engine | Engine-specific format | Engine API | Engine-specific | High — new service + DSL |
state_rules table with a state_code column and a
rules JSONB column covers every current requirement. Rules can be queried in SQL, updated
without a deploy, and versioned with a valid_from/valid_until timestamp pair
for legal effective-date tracking. A dedicated rules engine adds a DSL to learn and a service to operate
for if/then logic that Postgres handles natively.
Physical mail sending (certified letters to creditors, agencies, financial institutions) is a Tier 2 notification feature. It is not MVP. At MVP, Settle generates a correctly formatted letter as a PDF — with all addresses, legal language, and state-specific boilerplate — and the executor prints and mails it. This is explicitly presented to users as intentional, not a gap.
Lob charges approximately $1.50 per letter. An estate generating 10 letters × 2,000 estates/year = $30,000/year in direct mail costs. This must be priced into per-estate subscription tiers before the integration is built, not after. Do not build Lob integration until pricing is validated and the cost is explicitly passed through.
| Option | Cost / letter | API quality | Exit path |
|---|---|---|---|
| Lob | ~$1.50 | Excellent | PostGrid, DIY print vendor |
| PostGrid | ~$1.40 | Good | Lob, DIY print vendor |
| Click2Mail | ~$1.20 | Fair | Limited API; harder to migrate |
| PDF generation (MVP) Rec | $0 | N/A | N/A — trivial to add Lob later |
IMailDispatcher interface so the vendor can be swapped.
Death certificates, wills, financial statements, and account closure confirmations need to be stored.
At MVP scale, Postgres large objects (or bytea columns) are the correct answer.
500 estates × ~5 documents × ~500KB average = ~1.25 GB. This fits comfortably in any managed Postgres
plan and avoids an additional vendor relationship.
Settle's workflow engine processes 38-task dependency chains that run for 16–18 months per estate. Notification batches need scheduling. Neither of these requirements demands a separate queue service at MVP scale.
| Option | Infrastructure | Durable | Scheduled jobs |
|---|---|---|---|
| pg-boss Rec | Postgres only | Yes — transactional | Yes — cron syntax |
| BullMQ | Redis required | Yes | Yes |
| Inngest | Managed service | Yes | Yes |
| Temporal | Self-hosted or Cloud | Yes | Yes |
Required at MVP for: magic link auth, task reminders, and estate status notifications. The integration surface is intentionally minimal — a single HTTP POST per email.
| Option | Free tier | API | Exit path |
|---|---|---|---|
| Resend Rec | 3,000 emails/mo | Simple HTTP REST | Any SMTP provider |
| Postmark | 100 emails/mo | Simple HTTP REST | Any SMTP provider |
| SendGrid | 100 emails/day | REST + SMTP | Any SMTP provider |
| AWS SES | 62,000/mo (in-region) | AWS SDK | AWS-specific; harder to migrate |
send(to, subject, html)) so the provider can be swapped without touching application code.
Errors in estate workflows have real consequences for grieving families. A missed notification or a failed job is not a UX issue — it is a compliance risk. Error tracking is required from day one, not deferred.
pino or similar)
to stdout, captured by the hosting platform, cover the rest.
At Year 2 scale (5,000 estates), consider adding Grafana Cloud (free tier) for metrics and dashboards. Postgres slow query logging and pg-boss job failure rates are the first metrics that matter. Do not add OpenTelemetry before you have a problem to diagnose.
This is the highest-stakes infrastructure decision in this register. Settle stores SSNs, dates of
death, and financial account numbers. Column-level encryption via pgcrypto
(pgp_sym_encrypt) requires a symmetric key. Where that key lives determines the
blast radius of a database breach.
A leaked encryption key means all encrypted columns in the database are readable in plaintext. This is not a recoverable event. Treat key management as the most sensitive operational decision Settle makes.
| Option | Complexity | Breach blast radius | Cost |
|---|---|---|---|
| Environment variable (MVP) Rec | Low | High if env is leaked; low if only DB is breached | $0 |
| AWS KMS | Medium | Low — key never leaves KMS | ~$1/key/month |
| HashiCorp Vault | High | Low — key never leaves Vault | Self-hosted or ~$0.03/hr |
| Age / application-managed | Medium | Medium — depends on key storage | $0 |
Encryption Implementation Pattern
-- Column definition (never store plaintext SSN)
ALTER TABLE estate_contacts
ADD COLUMN ssn_encrypted TEXT;
-- Write (application layer, key from env)
UPDATE estate_contacts
SET ssn_encrypted = pgp_sym_encrypt($1, $2)
WHERE id = $3;
-- $2 = process.env.DB_ENCRYPTION_KEY
-- Read (decrypt only in application, not in views)
SELECT pgp_sym_decrypt(ssn_encrypted::bytea, $1)
FROM estate_contacts
WHERE id = $2;
Portability Assessment
Every vendor in the stack must have a documented exit path before it is adopted. This table defines that path for each MVP service. "Lock-in level" measures how much application code changes if the vendor is replaced.
| Service | Current vendor | Lock-in | Exit path | Migration effort |
|---|---|---|---|---|
| Hosting | Fly.io | Low | Any Docker host (Railway, Render, Cloud Run) | Update fly.toml → platform config. ~1 day. |
| Database | Fly Postgres | Low | pg_dump → restore to any Postgres |
Dump, restore, update DATABASE_URL. ~2 hours. |
| Auth | BetterAuth | Low | Replace session middleware; user data stays in app Postgres | Rewrite auth routes + middleware. ~2 days. |
| Resend | Low | Any SMTP or HTTP email provider | Swap 1 environment variable + 1 adapter file. ~1 hour. | |
| Error tracking | Sentry | Low | Datadog, Rollbar, self-hosted Sentry | Replace SDK import + DSN env var. ~1 hour. |
| Job queue | pg-boss | Low | BullMQ (+ Redis), Inngest, Temporal | Replace queue adapter; job logic unchanged. ~3 days. |
| Physical mail | PDF (deferred) | Low | Lob, PostGrid behind IMailDispatcher |
Implement interface. ~1 day. |
| Encryption keys | Env variable | Medium | Re-encrypt all rows after migrating to KMS/Vault | Decrypt all rows → re-encrypt with new key. ~1 day + downtime window. |
| Document storage | Postgres (deferred) | Low | R2 or S3 behind storage repository interface | Migrate blobs + swap interface implementation. ~2 days. |
Accounts & CLI Tools Needed
This is the complete list for local development and the first deploy. MVP requires five accounts.
The total time to create accounts and run docker compose up locally should be under 30 minutes.
MVP — Required Before First Commit
-
GitHub — source control, CI/CD via GitHub Actions
Free. Create at github.com. Install GitHub CLI:
brew install gh3 min -
Fly.io — hosting + managed Postgres (one account covers both)
Free tier. Create at fly.io. Install CLI:
brew install flyctl, thenflyctl auth login5 min -
Resend — transactional email (magic links, reminders)
Free tier: 3,000 emails/month. Create at resend.com. Copy API key to
.env. 3 min -
Sentry — error tracking and alerting
Free tier: 5,000 errors/month. Create at sentry.io. Copy DSN to
.env. 3 min - Domain registrar — DNS for settle.app or equivalent Cloudflare Registrar recommended (at-cost pricing, free DNS, proxy). ~$10/year for .app. 5 min
Local Development — Required Before Running the App
- Docker Desktop — runs Postgres locally via compose Install at docker.com/products/docker-desktop 5 min
-
Node.js 20 LTS — SvelteKit runtime
brew install nodeor usefnm/nvmfor version management 2 min -
Generate DB_ENCRYPTION_KEY — required before any SSN is written
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"— save to.envimmediately. Never commit. 1 min -
Copy
.env.exampleto.env— fill in all values DATABASE_URL, RESEND_API_KEY, SENTRY_DSN, DB_ENCRYPTION_KEY, SESSION_SECRET 2 min
Deferred — Do Not Create Yet
- Lob — physical mail API Deferred until Tier 2 notifications ship. ~$1.50/letter. Deferred
- Cloudflare R2 — object storage Deferred until Postgres document storage exceeds 5 GB. Deferred
- Redis / Upstash — job queue / cache Deferred until pg-boss throughput is a bottleneck. Deferred
- AWS KMS or HashiCorp Vault — key management Deferred until SOC 2 audit or Year 2 scale. Deferred
Minimum .env Template
# Database
DATABASE_URL=postgresql://settle:settle@localhost:5432/settle
# Encryption — generate with: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
DB_ENCRYPTION_KEY=<32-byte base64 string>
# Session
SESSION_SECRET=<random 64-char string>
# Email
RESEND_API_KEY=re_<your key>
EMAIL_FROM="Settle <notifications@yourdomain.com>"
# Error tracking
SENTRY_DSN=<your dsn>
NODE_ENV=development
Dependency Budget
The maximum external service count is 5 at MVP (6 counting Lob when it ships). This is a firm constraint, not a guideline. Every service added increases operational surface area for a solo or two-person team. Adding a service requires removing or justifying an existing one.
| # | Service | Phase | Justification |
|---|---|---|---|
| 1 | Fly.io (hosting + Postgres) | MVP | Non-negotiable — runs the app and stores all data |
| 2 | Resend | MVP | Auth magic links require email delivery |
| 3 | Sentry | MVP | Errors in estate workflows have compliance consequences |
| 4 | Cloudflare (DNS + domain) | MVP | Required for any public product |
| 5 | Lob | Post-MVP | Deferred — PDF generation suffices until user feedback says otherwise |
| — | Object storage, Redis, KMS, benefit APIs | Year 2+ | Each has a documented trigger condition; none are needed at MVP |
Cost Projection
Infrastructure costs at three stages. The goal is $0–25/month at MVP with generous free tiers carrying the load. The first significant cost step is Lob when Tier 2 ships — that cost must be priced into subscription tiers, not absorbed.
| Service | Free tier | MVP (<500 estates) | Year 2 (5,000 estates) | Year 3 (50,000 estates) |
|---|---|---|---|---|
| Fly.io (app + worker) | 3 shared VMs | ~$0 | ~$20/mo | ~$150/mo |
| Fly Postgres | Included | ~$0 | ~$15/mo | ~$60/mo |
| Resend | 3,000 emails/mo | ~$0 | ~$20/mo | ~$90/mo |
| Sentry | 5,000 errors/mo | ~$0 | ~$0 | ~$26/mo |
| Cloudflare (DNS + domain) | Free DNS | ~$10/yr | ~$10/yr | ~$10/yr |
| Lob (physical mail) post-MVP | None | $0 (deferred) | ~$2,500/mo * | ~$25,000/mo * |
| Object storage (R2) Year 2 | 10 GB free | $0 (deferred) | ~$0 | ~$15/mo |
| Infrastructure total | ~$1/mo | ~$55/mo + Lob pass-through | ~$340/mo + Lob pass-through |
At 5,000 estates/year × 10 letters/estate × $1.50 = $75,000/year in Lob costs. This cannot be absorbed as infrastructure overhead. It must be priced explicitly: either as a per-estate fee or as a Tier 2 add-on. Validate this pricing model before the Lob integration is built. If per-letter cost is unacceptable, evaluate PostGrid ($1.40) or a co-mailing arrangement at volume.
Open Questions
These questions are explicitly not answered yet. Each has a stated trigger — the event that forces the decision. Do not answer them early. Do not let them block MVP development.
-
Q-01 — Trigger: User feedbackWhen does print-and-mail friction become an integration requirement for Lob?Measure: support tickets citing print-and-mail difficulty, or funnel drop-off after PDF download. Threshold: >10% of estates abandon at the mail step, or >20 support tickets/month on the topic. Action: implement Lob behind
IMailDispatcher. Price per-estate tier to cover $15/estate in mail costs. -
Q-02 — Trigger: Database sizeWhen do documents move out of Postgres into dedicated object storage?Threshold: total document storage exceeds 5 GB or total document count exceeds 5,000, whichever comes first. Action: implement Cloudflare R2 behind a storage repository interface. Migration script: read each
byteablob, write to R2, replace with R2 URL. -
Q-03 — Trigger: User feedbackWhen does the manual benefit discovery checklist get automated?Threshold: users report the manual checklist as the most frustrating part of the 38-task plan, or benefit discovery rate is measurably lower than comparable estates completing it with professional help. Action: identify the 3–5 most valuable benefit APIs (SSA, PBGC, state unclaimed property) and integrate one at a time.
-
Q-04 — Trigger: Year 2 roadmapDoes Settle need a native mobile app?This affects the frontend framework choice. If yes: evaluate whether a React migration is warranted for React Native code sharing, or whether a separate Flutter app is preferable. If no: SvelteKit with a responsive PWA is sufficient. Decide before the Year 2 roadmap is written, not during it.
-
Q-05 — Trigger: Enterprise or institutional salesWhen does SOC 2 certification become a sales requirement?SOC 2 Type II requires: formal access controls, encryption key management (env variable is insufficient — move to KMS), audit logging, vendor agreements (BAAs), and a formal incident response policy. Estimated lead time: 6 months. Do not begin the process without a closed deal that requires it, or a clear pipeline of deals that do.
-
Q-06 — Trigger: Revenue validationWhat is the per-estate pricing model, and does it cover Lob costs at Year 2 scale?Required before Lob is integrated. The Lob cost at 5,000 estates/year is ~$75,000. A $200/estate subscription with 37.5% allocated to mail costs covers it. Validate actual mail volume per estate with the first 50 manually-processed estates before committing to an integration pricing model.