Portfolio/Blog

Operations CRM

A single-company internal operations platform that unifies Outlook mailboxes, support tickets, marketplace sales, finances, and AI-assisted workflows on top of an AWS serverless backend. Delivered as a static Next.js web app deployed via AWS Amplify.

Why I built it

The team was bouncing between Outlook, three marketplace dashboards (Amazon, eBay, Shopify), a spreadsheet for finances, and a separate ticket queue — each with its own login, schema, and notification noise. The original TicketSystem started as an Electron desktop app to consolidate Outlook + DynamoDB tickets, but distributing native installers and managing local credentials didn’t scale past a single workstation. Operations CRM is the rewrite: a static web app on Amplify that any teammate can open in a browser, backed by serverless Lambdas so there are no servers to babysit. The legacy Electron client has been retired.

Architecture

┌────────────────────────────────────────────────────────────┐
│   Static Next.js (S3 + CloudFront via AWS Amplify)         │
│   - output: 'export', no SSR                               │
│   - apps/OperationsCRM (Next.js 14)                        │
└──────┬──────────────────────────────────────────┬──────────┘
       │  fetch() with Cognito-issued JWT         │  WSS (MQTT-over-WebSocket,
       ▼                                          │  SigV4-signed via Cognito)
┌──────────────────────────────┐                  ▼
│   Amazon API Gateway         │      ┌────────────────────────────┐
└──────────────┬───────────────┘      │   AWS IoT Core             │
               │                      │   (WebSocket pub/sub)      │
               ▼                      └──────────────┬─────────────┘
┌────────────────────────────────────────────────────┴───────┐
│   AWS Lambda (infra/*-lambda)                              │
│   ├─ list-users-lambda     identity directory              │
│   ├─ outlook-oauth-lambda  OAuth flow + token refresh      │
│   ├─ sales-lambda          Amazon / eBay / Shopify orders  │
│   ├─ finance-lambda        revenue, P&L, ledger queries    │
│   ├─ sla-checker-lambda    ticket SLA + metrics aggregate  │
│   └─ assistant-lambda      AI assistant + RAG → Bedrock    │
└──────────────┬─────────────────────────────────┬───────────┘
               │                                 │
               ▼                                 ▼
┌──────────────────────────────┐   ┌────────────────────────────┐
│   Amazon DynamoDB            │   │   Amazon Bedrock           │
│   (tickets, sales, finance,  │   │   (Claude models for chat, │
│    auth tokens, RAG vectors) │   │    embeddings for RAG)     │
└──────────────────────────────┘   └────────────────────────────┘

   Auth: Amazon Cognito (client-side, JWT for HTTPS, SigV4 for IoT WSS)

Real-time updates (WebSockets via AWS IoT Core)

The frontend opens a single WSS connection to AWS IoT Core, signed with SigV4 using the Cognito identity, and subscribes to per-workspace MQTT topics:

Lambdas publish to these topics as a side effect of the work they’re already doing, so the UI updates the moment data lands in DynamoDB instead of waiting for the next poll. IoT Core handles connection state, reconnection, and fan-out, which is why there is no custom WebSocket service in the Lambda list.

Repository layout

Path Purpose
apps/OperationsCRM/ Next.js 14 static web client (the only frontend)
infra/ DynamoDB scripts, Cognito role setup, Lambda source
docs/ Product gap analysis and roadmap notes
amplify.yml Amplify build spec — produces dist/ from apps/OperationsCRM

Outlook integration

Microsoft Outlook is the team’s primary inbox, and it’s where tickets actually start. The outlook-oauth-lambda handles:

In the UI an agent sees their open conversations as a unified ticket queue, replies inline, and the reply goes back out via Graph. There is no separate webmail tab to manage.

Marketplace integrations — Amazon, eBay, Shopify

The sales-lambda is the single entry point for marketplace data. The frontend calls it for orders, revenue, and customer lookups, and it fans out to:

Marketplace data lands in DynamoDB on a normalized schema, so a ticket about an Amazon order and a ticket about a Shopify order render in the same view with the same fields. Order numbers detected in ticket bodies become deep links back to the originating marketplace dashboard.

AI assistant

A floating assistant available from any screen, with three things that make it useful instead of a toy:

Inference runs through Amazon Bedrock — Claude models for chat, drafting, and rewrites, and Bedrock embedding models for the RAG index. Tokens stream back to the browser over the IoT Core WebSocket so the assistant feels live instead of request/response.

The reply composer reuses the same stack: an agent writes a draft, hits the AI button, and gets a rewrite shaped by the brand voice and the cited RAG context. Word-level diffs let the agent accept or reject before sending.

Metrics dashboard

A reporting view backed by the sla-checker-lambda and the same DynamoDB-backed query layer the rest of the app uses. It surfaces the operational numbers a small ops team actually looks at:

The dashboard is a read view — no destructive actions — so it is safe to share with stakeholders who should not be editing tickets.

Tech stack