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
-
Landing Page (
/)- Hero with QR code preview animation
- Feature showcase (3 cards: Create, Share, Track)
- Social proof (# of cards generated)
- CTA: "Create Your Card"
-
Auth Pages (
/login,/register)- Split-screen design (form left, visual right)
- Email verification flow with resend option
- Password strength indicator (zxcvbn)
-
Dashboard (
/dashboard)- Card list (grid view, 3 columns)
- Quick actions: New Card, Share, Analytics
- Viral coefficient widget (referrals chart)
-
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)
-
Share Modal (overlay)
- Multi-email input with validation
- Custom message editor
- Send history table
-
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)
| Service | Usage | Cost |
|---|---|---|
| Cloud Run | 1M requests, 100 avg instances | $24 |
| Cloud SQL | db-f1-micro, 10GB storage | $15 |
| Cloud Storage | 100GB, 1M reads | $3 |
| SendGrid | 10K emails/month | $15 |
| Cloud CDN | 100GB egress | $8 |
| Total | ~$65/month |
Scales to $500/month at 10M requests, 100K users.
Open Questions for Clarification
- Logo Embedding: Should QR codes support custom logo overlays?
- Custom Domains: Allow users to use custom domains for QR URLs?
- Analytics Depth: Track scan location, device type, timestamp?
- Export Formats: Support CSV export of contact lists?
- Payment Model: Freemium (50 emails/day) vs paid tiers?
- Social Auth: Google/Microsoft OAuth for easier signup?
- API Access: Provide REST API for B2B integrations?
- 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