CASE STUDY

Simple Greeting-Card Web App (MVP Architecture)

3 min read·503 words·Beginner

Asked at

1 candidate report in Feb 2026

How to use this case study

SDE-2 / Mid

Design a minimal architecture (frontend, backend API, database, object storage) and the data model for templates, cards and recipients.

SDE-3 / Senior

Explain the send flow (email with a secure link), image uploads with pre-signed URLs, and basic security (unguessable links, rate limits).

Staff / Principal

Explain how the MVP evolves as usage grows (CDN, queues for email, scheduled sends, analytics) without over-engineering day one.


0) Problem Restatement

Atlassian asked a deliberately simple question: design a minimal web app for greeting cards. A user picks a template (birthday, thank you), customizes the message and maybe adds a photo, then sends it to a recipient by email. The recipient opens a link and sees the card. The interviewer wants a clean MVP (minimum viable product) architecture, not a massive distributed system, plus a sense of how it would grow.


1) Requirements

  • Browse templates. Create a card (template + message + optional image).
  • Send to one or more recipients by email (now, or scheduled).
  • The recipient views the card via a unique link, with no account needed.
  • The sender sees whether the card was opened.

1.1 Scale (MVP)

  • Thousands of cards per day, spiking around holidays. One region is fine.


2) MVP Architecture

Architecture Diagram

flowchart LR
    B["Browser - React SPA"] --> CDN["CDN - static app + template images"]
    B --> API["Backend API - one service"]
    API --> DB[("Postgres")]
    API --> OS[("Object storage - uploaded photos")]
    API --> MAIL["Email provider - SendGrid / SES"]
    R["Recipient"] -->|"card link"| API
  • Frontend: a single-page app served from a CDN.
  • Backend: one small service (e.g., Node, Python or Java) with a REST API. No microservices needed yet.
  • Database: Postgres for users, templates, cards and recipients.
  • Object storage for user photos, uploaded directly from the browser with a pre-signed URL.
  • Email provider API to send emails (don't run your own mail servers).


3) Data Model and APIs

templates:  template_id, name, category, image_url, layout_json
cards:      card_id, sender_id, template_id, message, photo_key, created_at
recipients: card_id, email, share_token (random, unguessable), sent_at, opened_at, scheduled_for
  • GET /templates
  • POST /cards { template_id, message, photo_key? }{ card_id }
  • POST /uploads → a pre-signed URL for the photo
  • POST /cards/{id}/send { recipients: [...], send_at? }
  • GET /c/{share_token} → the card page (records opened_at)


4) Send Flow

  1. Create a recipient row with a random share token (e.g., 128-bit, URL-safe). The link is https://cards.example/c/{token}, and it can't be guessed.
  2. Send the email through the provider with the link.
  3. When opened, render the card and set opened_at (only the first time).

Security basics: rate-limit sends per user (to stop spam), validate uploads (type and size), escape the message text (prevent script injection), and let links expire or be revoked if needed.

5) How It Grows

  • Holiday spikes: put email sending on a queue with workers (retries, provider rate limits), instead of sending inside the API request.
  • Scheduled sends: store scheduled_for, and a scheduler job enqueues due emails every minute.
  • More traffic: scale the stateless API horizontally behind a load balancer, add read replicas, and cache template lists.
  • Card images for previews: render cards to images asynchronously for email thumbnails and social sharing.
  • Analytics: log events (created, sent, opened) to an analytics store.


6) Wrap-Up

Start simple: an SPA on a CDN, one backend service, Postgres, object storage for photos (pre-signed uploads) and a managed email provider. Cards get recipient rows with unguessable share tokens, sent by email and tracked on open, with rate limits and input sanitization. Grow step by step: a queue for email, a scheduler for scheduled sends, horizontal scaling and caching, and async rendering.

More Case Studies

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with our AI interviewer.

Start System Design Interview →