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 /templatesPOST /cards{ template_id, message, photo_key? }→{ card_id }POST /uploads→ a pre-signed URL for the photoPOST /cards/{id}/send{ recipients: [...], send_at? }GET /c/{share_token}→ the card page (recordsopened_at)
4) Send Flow
- 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. - Send the email through the provider with the link.
- When opened, render the card and set
opened_at(only the first time).
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.