A·MIST Résumé ↗
← All work

Case study 03SaaS · Payments · Async2025 – 2026

A multi-tenant event platform that replaced paper, spreadsheets and manual payments for a client — registrations, verified payments, certificates and reminders, run by background workers.

Role
Architecture, backend, infra & product calls
Team
Two people, one live paying client
Runs on
One VPS · 5-container Docker Compose · GitHub Actions → GHCR
−35%
manual reconciliation
−40%
organiser follow-up
0
duplicate-charge incidents
Field notes

1contact per person, across every event — matched on email and phone through dozens of field-name aliases.rest
404not 403. The cross-tenant test proves Org B can’t even tell Org A’s data exists — after an audit that found and closed three leaks.rest
5containers on one VPS. The worker runs alone, so a certificate burst can’t starve the API.rest
10production incidents, each root-caused and turned into a standing rule rather than a one-off patch.rest
The architecture

One attendee, from an anonymous visit to an issued certificate — through dedup, their organiser’s own payment keys, a signed webhook and a worker that never blocks the request.

SmartFormFlow01 / 07

Public, unauthenticated, multi-step — and every field they touch is tracked.

Drop-off is recorded per field, so the funnel shows where people quit, not just how many.

Decisions & challenges · 05

  1. 01Shipped

    Closing three cross-tenant data leaks

    404, not 403

    Problem
    The multi-tenancy migration exposed three leaks: forms filtered by userId, and two lookups with no organisation check at all.
    Approach
    A manual audit of every backend system; organizationId on every where-clause; isolation tests that expect 404 so another org’s data looks like it doesn’t exist.
    Outcome
    Zero leaks in audited endpoints, with the isolation test as a pre-deploy gate.
  2. 02Shipped

    Normalising real-world contact fields

    1 contact / person

    Problem
    Dedup only matched fields literally named “phone” or “email” — “Mobile Number” or “WhatsApp No.” quietly created duplicate people.
    Approach
    A field-alias registry mapping dozens of real label variants to canonical phone and email, with near-miss logging to grow it from real usage.
    Outcome
    Deduplication that works on how people actually label forms.
  3. 03Shipped

    A silent Razorpay receipt-limit failure

    40 chars

    Problem
    Order creation failed for some organisations with no error at all: the receipt field has a 40-character limit that prefixed UUIDs exceeded.
    Approach
    Strip dashes, keep 30 characters, prefix “rcpt_” — once, at the API boundary. Amounts stay in display units everywhere else; paise only at the call.
    Outcome
    Reliable orders across orgs, and one place for unit conversion.
  4. 04Shipped

    Three silent failures, three standing rules

    3 rules

    Problem
    SMTP broke when Compose read “$” as a variable, Nodemailer bound before env vars loaded, and certificate jobs died on a dynamic import.
    Approach
    Quote “$” in .env; build the transporter lazily on first use; static top-level imports across the worker layer.
    Outcome
    Each incident became a rule instead of a patch — the quoting rule alone has prevented several repeats.
  5. 05Shipped

    Two-phase migrations on live client data

    0 data lost

    Problem
    Schema changes had to land on a live client’s data, where migrate-dev or hand-edited migrations could corrupt history or rows.
    Approach
    Phase 1: additive, nullable columns applied directly and marked resolved. Phase 2: constraints and indexes only after deploy and backfill.
    Outcome
    No data loss across every migration since — now the standing protocol.
Next case studyQuizBuzzA multi-tenant platform for large, live, proctored quiz contests — payments, WebSockets, queues and auto-scaling infrastructure, built solo from the first diagram to the load test.Read the case study