Skip to main content

QR Contact Card Generator - Production Specification v1

System Overview

Build a viral QR-based contact card generator that enables users to create scannable vCard QR codes and self-promote through email distribution. The system must handle viral growth patterns (1000+ simultaneous generations) with <100ms p95 latency for QR generation.

Architecture Requirements

Frontend Stack

  • Framework: React 18+ (TypeScript strict mode)
  • UI Library: Chakra UI v2+ with custom theme
  • WASM Module: Rust-compiled QR generation (image-rs + qrcode crate)
  • State Management: Zustand for global state, React Query for server state
  • Form Validation: Zod schemas matching backend contracts
  • Build: Vite with WASM plugin, target ES2022

Backend Stack

  • Runtime: GCP Cloud Run (Rust, min 1 instance, max 100)
  • Framework: Axum 0.7+ with Tower middleware
  • Database: PostgreSQL 15+ (Cloud SQL) - NOT FoundationDB
    • Rationale: User management doesn't require FoundationDB's distributed guarantees
    • Cost: $0.017/hour vs FoundationDB complexity
    • Cloud SQL provides automatic backups, point-in-time recovery
  • Caching: Redis (Memorystore) for session management, rate limiting
  • Email Service: SendGrid API (100 emails/day free, scales to viral)
  • Storage: GCS bucket for generated QR images (CDN-backed)
  • Observability: Cloud Trace, Cloud Logging, Prometheus metrics

Data Models

User Schema (PostgreSQL)

CREATE TABLE users (
user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL, -- Argon2id
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
email_verified BOOLEAN DEFAULT FALSE,
last_login TIMESTAMPTZ
);

CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at);

Contact Card Schema

CREATE TABLE contact_cards (
card_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(user_id) ON DELETE CASCADE,

-- vCard 4.0 fields
full_name VARCHAR(255) NOT NULL,
organization VARCHAR(255),
title VARCHAR(255),
email VARCHAR(255) NOT NULL,
phone VARCHAR(50),
website VARCHAR(500),

-- QR Configuration
qr_error_correction VARCHAR(10) DEFAULT 'M', -- L, M, Q, H
qr_image_url TEXT NOT NULL, -- GCS CDN URL
qr_size INTEGER DEFAULT 512, -- pixels

-- Metadata
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
view_count INTEGER DEFAULT 0,
scan_count INTEGER DEFAULT 0 -- tracked via analytics endpoint
);

CREATE INDEX idx_contact_cards_user ON contact_cards(user_id);

Viral Distribution Schema

CREATE TABLE viral_invitations (
invitation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sender_user_id UUID REFERENCES users(user_id),
recipient_email VARCHAR(255) NOT NULL,
sent_at TIMESTAMPTZ DEFAULT NOW(),
opened_at TIMESTAMPTZ,
converted_at TIMESTAMPTZ, -- recipient created account

UNIQUE(sender_user_id, recipient_email, sent_at::DATE) -- 1 email/day limit
);

CREATE INDEX idx_viral_sender ON viral_invitations(sender_user_id);
CREATE INDEX idx_viral_conversion ON viral_invitations(converted_at) WHERE converted_at IS NOT NULL;

API Specifications

Authentication Endpoints

POST /api/v1/auth/register
{
"email": "user@example.com",
"password": "min12chars!Upper1",
"full_name": "John Doe"
}
Response: { "user_id": "uuid", "token": "jwt", "expires_in": 86400 }

POST /api/v1/auth/login
{
"email": "user@example.com",
"password": "password"
}
Response: { "token": "jwt", "expires_in": 86400 }

POST /api/v1/auth/verify-email
{
"token": "verification-token-from-email"
}

Contact Card Endpoints

POST /api/v1/cards
Authorization: Bearer <jwt>
{
"full_name": "John Doe",
"organization": "Coditect.ai",
"title": "Software Architect",
"email": "john@coditect.ai",
"phone": "+1-555-0100",
"website": "https://coditect.ai",
"qr_config": {
"error_correction": "H", // High for logo embedding
"size": 1024,
"format": "png"
}
}
Response: {
"card_id": "uuid",
"qr_image_url": "https://cdn.coditect.ai/qr/uuid.png",
"vcard_data": "BEGIN:VCARD...",
"expires_at": "2025-12-31T23:59:59Z" // CDN cache
}

GET /api/v1/cards/:card_id
Authorization: Bearer <jwt>
Response: { card details + analytics }

PUT /api/v1/cards/:card_id
Authorization: Bearer <jwt>
{ updated fields }

DELETE /api/v1/cards/:card_id
Authorization: Bearer <jwt>

Viral Distribution Endpoints

POST /api/v1/cards/:card_id/share
Authorization: Bearer <jwt>
{
"recipients": ["friend1@example.com", "friend2@example.com"],
"message": "Check out my digital business card!" // Optional
}
Response: {
"sent": 2,
"failed": 0,
"rate_limited": false,
"next_available": null // ISO timestamp if rate limited
}

// Rate Limits:
// - 50 emails per user per day
// - 5 emails per recipient per sender per day
// - Exponential backoff: 1min, 5min, 15min, 1hr

Analytics Endpoint

POST /api/v1/analytics/scan
{
"card_id": "uuid",
"user_agent": "Mozilla/5.0...",
"referrer": "optional"
}
// No auth required - public endpoint
// Records scan event for viral coefficient tracking

Frontend Components

Core Pages

  1. Landing Page (/)

    • Hero with QR code preview animation
    • Feature showcase (3 cards: Create, Share, Track)
    • Social proof (# of cards generated)
    • CTA: "Create Your Card"
  2. Auth Pages (/login, /register)

    • Split-screen design (form left, visual right)
    • Email verification flow with resend option
    • Password strength indicator (zxcvbn)
  3. Dashboard (/dashboard)

    • Card list (grid view, 3 columns)
    • Quick actions: New Card, Share, Analytics
    • Viral coefficient widget (referrals chart)
  4. Card Editor (/cards/new, /cards/:id/edit)

    • Live QR preview (updates on input change)
    • Form sections: Personal, Professional, QR Settings
    • Preview mode (mobile viewport simulation)
  5. Share Modal (overlay)

    • Multi-email input with validation
    • Custom message editor
    • Send history table
  6. Profile Settings (/profile)

    • Email change (requires verification)
    • Password reset
    • Account deletion (with confirmation)

Shared Components

// Layout
<AppShell>
<Header>
<Logo position="left" />
<HamburgerMenu>
<NavLinks />
<ThemeToggle />
</HamburgerMenu>
</Header>
<MainContent />
<Footer>
<Copyright text="© 2025 Coditect.ai" />
<ContactLink email="contact@coditect.ai" />
</Footer>
</AppShell>

// Theme Configuration
const theme = {
colors: {
brand: { light: '#3182CE', dark: '#63B3ED' },
background: { light: '#FFFFFF', dark: '#1A202C' },
surface: { light: '#F7FAFC', dark: '#2D3748' }
},
fonts: {
heading: 'Inter, system-ui, sans-serif',
body: 'Inter, system-ui, sans-serif',
mono: 'JetBrains Mono, monospace'
}
}

Security Requirements

Authentication

  • Password Policy: Min 12 chars, 1 upper, 1 lower, 1 number, 1 special
  • Hashing: Argon2id (m=64MB, t=3, p=4)
  • JWT: HS256, 24hr expiry, refresh token pattern
  • Rate Limiting: 5 login attempts per 15min per IP

Authorization

  • Middleware: JWT validation on all /api/v1/* except public endpoints
  • RBAC: User can only access own cards and profile
  • API Keys: Future consideration for B2B integrations

Data Protection

  • Encryption at Rest: GCP default (AES-256)
  • Encryption in Transit: TLS 1.3 minimum
  • PII Handling: Email masked in logs, no phone/address in logs
  • GDPR Compliance: Export/delete functionality, cookie consent

Input Validation

  • Email: RFC 5322 compliant, MX record verification optional
  • Phone: E.164 format recommended, not enforced
  • URL: HTTPS preferred, sanitize for XSS
  • QR Size: 256-2048px range
  • File Upload: If logo embedding added, max 100KB, PNG/JPG only

Performance Requirements

Latency Targets

  • QR Generation: <50ms p95 (WASM client-side)
  • API Response: <100ms p95 (database queries)
  • Email Send: <500ms p95 (async with SendGrid)
  • Image Upload to GCS: <200ms p95

Throughput

  • Concurrent Users: 1000+ (Cloud Run autoscaling)
  • QR Generations: 100/sec sustained
  • Email Bursts: 500/min with queue

Scalability

  • Horizontal: Cloud Run scales 1→100 instances
  • Database: Connection pooling (max 20 per instance)
  • Cache: Redis for session store (10K ops/sec)
  • CDN: Cloud CDN for QR images (global distribution)

Observability Strategy

Metrics (Prometheus)

// Custom metrics
http_requests_total{method, path, status}
qr_generation_duration_seconds{error_correction}
email_send_total{status="success|failed"}
viral_conversion_rate{window="24h|7d|30d"}
db_connection_pool_size{state="active|idle"}

Logging (Structured JSON)

{
"timestamp": "2025-10-03T12:34:56Z",
"level": "INFO",
"service": "qr-generator-api",
"trace_id": "abc123",
"user_id": "uuid",
"event": "card_created",
"card_id": "uuid",
"latency_ms": 45
}

Tracing

  • Cloud Trace integration via OpenTelemetry
  • Trace all API requests with spans:
    • DB query
    • QR generation
    • GCS upload
    • Email send

Alerting

  • Error Rate: >1% errors for 5min → PagerDuty
  • Latency: p95 >200ms for 5min → Slack
  • Database: Connection pool >80% for 2min → Slack
  • Email Failures: >5% failure rate → Slack

Deployment Strategy

Infrastructure as Code (Terraform)

# Cloud Run service
resource "google_cloud_run_service" "qr_api" {
name = "qr-generator-api"
location = "us-central1"

template {
spec {
containers {
image = "gcr.io/project/qr-api:latest"
resources {
limits = {
cpu = "1000m"
memory = "512Mi"
}
}
env {
name = "DATABASE_URL"
value_from {
secret_key_ref {
name = "database-url"
key = "latest"
}
}
}
}
container_concurrency = 80
timeout_seconds = 30
}

metadata {
annotations = {
"autoscaling.knative.dev/minScale" = "1"
"autoscaling.knative.dev/maxScale" = "100"
}
}
}
}

# Cloud SQL instance
resource "google_sql_database_instance" "main" {
name = "qr-generator-db"
database_version = "POSTGRES_15"
region = "us-central1"

settings {
tier = "db-f1-micro" # Start small, scale later

backup_configuration {
enabled = true
start_time = "03:00"
}
}
}

CI/CD Pipeline (GitHub Actions)

name: Deploy

on:
push:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Rust tests
run: cargo test --all
- name: Run TypeScript tests
run: npm test

deploy:
needs: test
runs-on: ubuntu-latest
steps:
- name: Build Docker image
run: docker build -t gcr.io/$PROJECT/qr-api:$SHA .
- name: Push to GCR
run: docker push gcr.io/$PROJECT/qr-api:$SHA
- name: Deploy to Cloud Run
run: gcloud run deploy qr-api --image gcr.io/$PROJECT/qr-api:$SHA

Viral Mechanism Design

Email Template

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{sender_name}} shared their contact card</title>
</head>
<body style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
<h2>{{sender_name}} wants to share their contact with you</h2>

<div style="background: #f7fafc; padding: 20px; border-radius: 8px;">
<img src="{{qr_image_url}}" alt="QR Code" style="max-width: 300px;" />
<p><strong>{{sender_name}}</strong></p>
<p>{{sender_organization}} - {{sender_title}}</p>
</div>

<p>Scan this QR code with your phone camera to save {{sender_name}}'s contact.</p>

<hr />

<p style="color: #666;">
Want to create your own digital business card?
<a href="https://qr.coditect.ai/signup?ref={{sender_user_id}}">
Get started free
</a>
</p>

<p style="font-size: 12px; color: #999;">
You received this because {{sender_name}} ({{sender_email}}) shared their contact.
<a href="{{unsubscribe_url}}">Unsubscribe</a>
</p>
</body>
</html>

Viral Coefficient Tracking

// Calculate viral coefficient every 24 hours
// K = (invitations_sent * conversion_rate) / time_period
// Target: K > 1.0 for viral growth

async fn calculate_viral_coefficient(period_days: i32) -> f64 {
let stats = sqlx::query!(
r#"
SELECT
COUNT(DISTINCT sender_user_id) as senders,
COUNT(*) as invitations_sent,
COUNT(converted_at) as conversions
FROM viral_invitations
WHERE sent_at > NOW() - INTERVAL '%s days'
"#,
period_days
)
.fetch_one(&pool)
.await?;

let conversion_rate = stats.conversions as f64 / stats.invitations_sent as f64;
let k = (stats.invitations_sent as f64 / stats.senders as f64) * conversion_rate;

// Record in metrics
metrics::gauge!("viral_coefficient", k, "period" => period_days.to_string());

k
}

Testing Strategy

Backend Tests

#[cfg(test)]
mod tests {
#[tokio::test]
async fn test_qr_generation() {
let vcard = generate_vcard(...);
let qr = generate_qr_code(&vcard, ErrorCorrection::High, 512);
assert!(qr.len() > 1000); // Non-empty image
}

#[tokio::test]
async fn test_rate_limiting() {
// Send 51 emails (limit is 50)
for i in 0..51 {
let result = send_viral_email(...).await;
if i < 50 {
assert!(result.is_ok());
} else {
assert!(matches!(result, Err(Error::RateLimited)));
}
}
}

#[tokio::test]
async fn test_auth_flow() {
let user = register_user(...).await?;
assert!(!user.email_verified);

verify_email(user.verification_token).await?;
let user = get_user(user.id).await?;
assert!(user.email_verified);
}
}

Frontend Tests

describe('QRCardForm', () => {
it('validates email format', () => {
render(<QRCardForm />);
const emailInput = screen.getByLabelText('Email');

fireEvent.change(emailInput, { target: { value: 'invalid' } });
expect(screen.getByText('Invalid email')).toBeInTheDocument();
});

it('generates QR code on valid input', async () => {
const { container } = render(<QRCardForm />);

// Fill form
fireEvent.change(screen.getByLabelText('Full Name'), {
target: { value: 'John Doe' }
});
// ... fill other fields

fireEvent.click(screen.getByText('Generate QR'));

await waitFor(() => {
const qrImage = container.querySelector('img[alt="QR Code"]');
expect(qrImage).toBeInTheDocument();
});
});
});

Cost Estimation (Monthly)

ServiceUsageCost
Cloud Run1M requests, 100 avg instances$24
Cloud SQLdb-f1-micro, 10GB storage$15
Cloud Storage100GB, 1M reads$3
SendGrid10K emails/month$15
Cloud CDN100GB egress$8
Total~$65/month

Scales to $500/month at 10M requests, 100K users.

Open Questions for Clarification

  1. Logo Embedding: Should QR codes support custom logo overlays?
  2. Custom Domains: Allow users to use custom domains for QR URLs?
  3. Analytics Depth: Track scan location, device type, timestamp?
  4. Export Formats: Support CSV export of contact lists?
  5. Payment Model: Freemium (50 emails/day) vs paid tiers?
  6. Social Auth: Google/Microsoft OAuth for easier signup?
  7. API Access: Provide REST API for B2B integrations?
  8. White-Label: Allow companies to rebrand for internal use?

Changes from Original Prompt:

  • Replaced FoundationDB with PostgreSQL (cost/complexity appropriate)
  • Added comprehensive API specifications
  • Defined security requirements (authentication, authorization, encryption)
  • Specified performance targets and observability strategy
  • Included viral mechanism implementation details
  • Added deployment and CI/CD strategy
  • Provided cost estimation and scalability plan