Skip to content

Technical Stack

You don’t need to be a developer to read this page. But if you are, you’ll appreciate why every tool earned its spot. The guiding principle: minimize cost, maximize speed, and keep things production-ready from day one on AWS.

Every component was chosen to minimize cost, maximize development speed, and stay operationally simple for a solo technical founder — while running on AWS infrastructure you fully control.

ComponentChoiceWhyMonthly Cost
FrontendNext.js 14 + Tailwind + shadcn/uiFast to build, server components for speed, great DX. Hosted on AWS Amplify or S3 + CloudFront.~$5-20
Backend APIGo (Golang)Fast, type-safe, compiles to a single binary. Runs on ECS Fargate — no servers to manage. Excellent concurrency model for handling async AI/email workflows.~$15-30 (Fargate)
DatabaseAWS RDS PostgreSQL (Multi-AZ)Fully managed Postgres with automated backups, encryption at rest + in transit, and point-in-time recovery. Multi-tenant via schema-per-tenant with strict Row-Level Security.~$30-50 (db.t4g.micro/small)
AuthCustom Go auth layer + Google OAuthLightweight JWT-based auth implemented in Go. No third-party auth dependency — full control over session management and tenant isolation.Included in backend
AIClaude Sonnet 4.6 APIBest cost/quality ratio for structured tasks. ~$0.003/1K input tokens~$200-500 at scale
Job queueNATS JetStreamDurable messaging with at-least-once delivery, consumer groups, and replay. Runs as a sidecar on Fargate — lightweight, fast, and zero external dependencies.Included in Fargate cost
Email sendingResend (or Amazon SES)Modern API, good deliverability, easy domain verification. SES as fallback for cost optimization at scale.$20 (Resend) or ~$1/10K emails (SES)
Email receivingGmail/Outlook OAuth APIRead replies directly, no proxy neededFree
Prospect dataApollo.io APIBest price/quality for SMB prospect data. $0.03-0.05/enrichment~$100-300 at scale
Domain mgmtCloudflareDNS + email routing for sending domainsFree-$5
MonitoringSentry + PostHogError tracking + product analyticsFree tiers
SecretsAWS Secrets ManagerEncrypted storage for API keys, DB credentials, OAuth tokens. Rotated automatically.~$2

Spear serves many customers from a single deployment, so tenant isolation is non-negotiable. We use a schema-per-tenant model on RDS PostgreSQL with multiple layers of safeguards.

SafeguardImplementationWhat It Prevents
Schema-per-tenantEach tenant gets their own Postgres schema (tenant_{id}). Tables, indexes, and data are fully isolated.Cross-tenant data leaks from shared tables
Row-Level Security (RLS)RLS policies on every table enforce current_setting('app.tenant_id') = tenant_id. Even a buggy query can’t return another tenant’s rows.Accidental cross-tenant reads/writes
Dedicated DB rolesEach tenant schema has a restricted Postgres role with USAGE and SELECT/INSERT/UPDATE/DELETE only on their schema. No access to public or other tenant schemas.Privilege escalation between tenants
Connection-level tenant bindingEvery connection sets SET app.tenant_id = '{id}' before executing any queries. RLS policies reference this session variable.Queries running without tenant context
Encrypted at rest + in transitRDS encryption enabled (AES-256). All connections require SSL (sslmode=require).Data exposure from disk or network sniffing
Automated backups + PITRDaily automated snapshots with 7-day retention. Point-in-time recovery to the second.Data loss from bugs, bad migrations, or incidents
Audit loggingpgaudit extension logs all DDL and DML. CloudWatch Logs for long-term retention.Unauthorized changes going undetected
SafeguardImplementationWhat It Prevents
Tenant-aware middlewareEvery HTTP request extracts tenant ID from the JWT and injects it into the request context. No tenant ID = request rejected.Unauthenticated or tenant-ambiguous queries
Scoped DB connectionsA TenantDB(ctx) helper returns a connection pre-configured with SET app.tenant_id and SET search_path = tenant_{id}, shared. All repository methods use this — never a raw pool connection.Forgetting to set tenant context
Compile-time tenant enforcementRepository interfaces require context.Context as the first parameter. The context carries the tenant ID. Linting rules flag any direct db.Query() calls that bypass the tenant-aware wrapper.Developers accidentally querying without tenant scope
Parameterized queries onlyAll queries use $1, $2 placeholders via pgx. No string interpolation, ever. SQL injection is structurally impossible.SQL injection attacks
Migration safetySchema migrations run per-tenant using golang-migrate with a tenant-aware wrapper. Migrations are tested against a staging tenant before rolling out.Broken migrations corrupting tenant data
Connection poolingPgBouncer in front of RDS in transaction mode. Limits total connections and prevents connection exhaustion from tenant spikes.One tenant’s traffic starving others

All async work — prospect research, email generation, send scheduling, reply processing — flows through NATS JetStream with durable consumers.

Design DecisionImplementationWhy
Durable consumersNamed consumers with explicit ack. Messages re-deliver if not acknowledged within 30 seconds.No lost jobs, even if a worker crashes mid-task
Streams per domainSeparate streams for PROSPECTS, EMAILS, REPLIES, ANALYTICS. Each has its own retention and consumer groups.Clear separation of concerns, independent scaling
At-least-once deliveryAckExplicit policy with MaxDeliver: 5 and exponential backoff. Dead-letter stream (DLQ) after 5 failures.Guaranteed processing without infinite retry loops
Tenant-tagged messagesEvery message header includes Spear-Tenant-ID. Consumers validate tenant context before processing.Tenant isolation extends to the queue layer
Replay capabilityStreams retain messages for 72 hours. Can replay from any sequence number for debugging or reprocessing.Recover from bugs without data loss
Lightweight deploymentNATS runs as a sidecar container alongside the Go backend on Fargate. Single binary, ~20MB memory footprint.No external queue service to manage or pay for

At Launch

~$150/mo total infrastructure. RDS micro + Fargate spot + Cloudflare + free tiers. Enough to serve first 10-20 customers while staying lean.

At 100 Customers

$8-15/mo marginal per customer (AI calls + data enrichment). Total infra ~$3,500/mo. RDS scales to db.t4g.medium, Fargate adds workers.

At 1,000 Customers

$5-10/mo marginal per customer (volume discounts). Total infra ~$75,000/mo with 84% gross margin. Multi-AZ RDS, multiple Fargate tasks, NATS cluster.