🌐 Network Topology

A network-level view of how the platform is laid out — droplets, ports, and the private VPC. All public traffic enters through the Cloudflare edge (WAF · DDoS · bot filtering), which proxies DNS and hides origin IPs; TLS to origin is Full (strict). Everything runs inside one DigitalOcean VPC in London (LON1), split across three droplets. There are two public entry points, both Nginx terminating TLS on :443: the App Droplet (storefront, admin, slot-allocation, docs, landing page, Medusa API, SSO auth) and the ERP Droplet (ERP web :8069 / longpoll :8072 upstream). Static images are offloaded to a DigitalOcean Spaces CDN (sesame-marketplace-cdn.lon1) that browsers fetch directly, bypassing the origin droplets. Within a droplet, services talk over localhost; across droplets they use the private VPC network only (Medusa ↔ DES, DES Worker → ERP). Datastores are never publicly reachable — the DO Managed Postgres is VPC-only, and Redis / RabbitMQ / the SSO auth & ERP Postgres bind to their host. SSH is RSA-key only on :22; password authentication is disabled on all servers.

System Overview - Production Environment
flowchart TB INET["🌐 Public Internet
Buyers · Producers · Admin · Slot-allocation · Ops users"] subgraph CF["☁️ Cloudflare (edge)"] WAF["WAF · DDoS · Bot filter
DNS proxy — hides origin IPs
TLS to origin: Full (strict)"] end SPACES["🗄️ DigitalOcean Spaces (CDN)
sesame-marketplace-cdn.lon1
static images — frontends load direct"] subgraph VPC["🔒 DigitalOcean VPC — LON1 (private network 10.x)"] direction TB subgraph APP["🖥️ App Droplet — Ubuntu"] direction TB NGINXA["⬡ Nginx :443 (TLS, Let's Encrypt)
public entry point
vhosts: app · admin · slots · docs · api · auth · landing"] subgraph APPSVC[" "] direction LR SPA["Static SPAs (served from disk)
storefront · admin · slot-alloc · docs · landing"] MEDA["Medusa :9000 (PM2 cluster)"] KCA["SSO Auth server :8080 (Docker)"] end REDIS[("Redis :6379
event bus · cache · locks")] end subgraph ODOOD["🟢 ERP Droplet — Ubuntu"] direction TB NGINXO["⬡ Nginx :443 (TLS)
public entry point"] ODOOA["ERP System (Docker)
web :8069 · longpoll :8072"] ODOOPG[("ERP PostgreSQL :5432
(Docker, local to droplet)")] end MPG[("DO Managed PostgreSQL :5432
Sesame DB (Medusa) · TLS, VPC-only")] KCPG[("SSO Auth PostgreSQL :5432")] subgraph DESD["🟣 DES Droplet — Ubuntu"] direction TB DESSA["DES Server :3000 (PM2)"] DESWA["DES Worker (PM2)"] RMQA["RabbitMQ :5672
mgmt :15672"] end end %% ── Public ingress (network reachability only) ── INET -->|HTTPS 443| WAF WAF -->|"443 → App Droplet"| NGINXA WAF -->|"443 → ERP Droplet"| NGINXO %% ── Browsers pull static images straight from the CDN (bypasses origin) ── INET -.->|"HTTPS · images"| SPACES %% ── Nginx terminates TLS, forwards on localhost ── NGINXA -->|localhost| SPA NGINXA -->|"localhost :9000"| MEDA NGINXA -->|"localhost :8080"| KCA NGINXO -->|"localhost :8069 / :8072"| ODOOA %% ── Datastore links (private) ── MEDA -->|VPC / TLS| MPG MEDA -->|localhost| REDIS KCA --> KCPG ODOOA -->|localhost| ODOOPG %% ── Cross-droplet traffic stays on the private VPC network ── MEDA -.->|"VPC private net → :3000"| DESSA DESWA -.->|"VPC private net → :443"| NGINXO DESWA -.->|"VPC private net → :9000"| MEDA %% ── Ops access ── ADMINSSH["🔑 SSH — RSA key only
password auth disabled"] ADMINSSH -.->|":22"| APP ADMINSSH -.->|":22"| DESD ADMINSSH -.->|":22"| ODOOD %% ── Styling ── classDef entry stroke:#D3A163,stroke-width:3px; classDef store fill:#E8F0FF,stroke:#1B5FA8; classDef edge fill:#FFF4EC,stroke:#F6821F,stroke-width:1.5px; class NGINXA,NGINXO entry; class MPG,KCPG,ODOOPG,REDIS,RMQA store; class WAF,SPACES edge;
↔️ Nginx Route & Port Mappings

All inbound traffic arrives on port 443 (HTTPS). Nginx terminates TLS via Let's Encrypt and routes internally on localhost. Port 80 issues a 301 redirect to HTTPS on all vhosts. Static frontends are served directly from disk — no proxy hop. Odoo is not behind Nginx — it runs on its own droplet with ports 8069 and 8072 exposed directly on the host.

DomainServiceTypeInternal targetLocationNotes
qa.api.sesame.market Medusa Backend reverse proxy localhost:9000 location / All requests proxied to Medusa PM2 process
qa.app.sesame.market Storefront (simplifab-v2) static location / try_files $uri /index.html · root: /home/deploy/app/current · static assets cached 1y · gzip + security headers
qa.admin.sesame.market Admin UI static location / try_files $uri /index.html · root: /home/deploy/admin/current · gzip + security headers
qa.logistics.sesame.market Logistics Dashboard static location / try_files $uri /index.html · root: /home/deploy/logistics/current · /assets/ cached 1y immutable
qa.dataexchange.sesame.market Data Exchange Service reverse proxy 127.0.0.1:3000 location /
location = /api/webhook
Full proxy to DES Server · webhook endpoint explicitly routed to same target · WebSocket headers forwarded · max body 10 MB
qa.tcms.sesame.market TCMS reverse proxy TODO Config not provided — inferred Next.js proxy
qa.opshub.sesame.market Odoo ERP direct — no Nginx Ports 8069 & 8072 exposed directly on the Odoo Droplet host — not routed through Nginx
DomainServiceTypeInternal targetLocationNotes
api.sesame.market Medusa Backend reverse proxy localhost:9000 location / All requests proxied to Medusa PM2 process
alpha.app.sesame.market Storefront (simplifab-v2) static location / try_files $uri /index.html · root: /home/deploy/app/current
TODO Admin UI static Domain not yet configured
TODO Logistics Dashboard static Domain not yet configured
TODO Data Exchange Service reverse proxy 127.0.0.1:3000 Domain not yet configured
TODO TCMS reverse proxy TODO N/A — internal tool, not production-facing
alpha.opshub.sesame.market Odoo ERP direct — no Nginx Ports 8069 & 8072 exposed directly on the Odoo Droplet host — not routed through Nginx
🔐 Network Security Controls
  • TLS everywhere — Let's Encrypt, auto-renew via certbot. Port 443 only.
  • Password SSH disabled — RSA key-based access only on all servers.
  • Database not exposed — PostgreSQL accessible only within VPC private network.
  • Internal service comms — Medusa ↔ DES ↔ Odoo communicate over localhost / VPC-internal network only.
  • Separate VPCs — Production and QA are isolated VPCs; no cross-environment traffic.

Pending security gaps and go-live blockers are tracked in Security Index.

⚙️ Application Architecture

The detailed system architecture — who talks to whom, over what protocol, in which direction. Medusa v2.12.5 is the core commerce engine (16 custom modules, 70 workflows) backed by DO Managed PostgreSQL and Redis (event bus, workflows, cache, locks). All four SPAs are static builds served by Nginx. Authentication is split: Admin, Slot-Allocation and Docs sign in via SSO authentication (OIDC + PKCE, realm sesame) — Admin/Slot-Allocation then exchange the SSO token for a Medusa JWT — while Buyers/Producers use Medusa's native email/password auth. Static images (product & marketing) are served from a DigitalOcean Spaces CDN that browsers load directly; the Image Resizer utility converts/resizes in-browser and publishes optimised assets to that CDN. A standalone Landing Page captures sales leads and writes them into the same Sesame database. The Data Exchange Service (DES) is a stateless queue broker (no DB of its own) that bridges Medusa ⇄ Odoo bidirectionally over RabbitMQ: Medusa webhooks push orders/customers/producers → Odoo, and Odoo webhooks push products/fulfillment/marketing → Medusa. Detrack handles last-mile delivery, integrated through Odoo (job create out, status webhook in, relayed back to Medusa). The storefront also emits Google Analytics events (gtag). TCMS (qa-test-manager) is a standalone internal test-case tool (Next.js, own Postgres, ClickUp sync). Coming / WIP: a Route Planner (in progress) and Sesame 360.

Service Interaction Map
flowchart LR %% ═══════════ ACCESS (far left) — Clients NEXT TO Frontend SPAs ═══════════ subgraph ACCESS[" "] direction LR subgraph CLIENTS["👥 Clients"] direction LR BUYER["🛒 Buyer
storefront login"] PRODUCER["🏭 Producer
storefront login (supplier)"] ADMINU["🧑‍💼 Admin User"] LOGU["🚚 Slot-Allocation User"] DOCSU["📖 Docs Reader"] LEADU["📣 Sales Lead (visitor)"] end subgraph FE["🖥️ Frontend SPAs (static, Nginx)"] direction LR SF["Storefront
simplifab-v2 · React"] ADM["Admin UI
administration · React"] LOG["Slot-Allocation UI
React"] DOCS["Docs Hub
static · SSO (JS adapter)"] LAND["Landing Page
static · sales lead capture"] end end SSO["🔑 SSO Authentication
realm: sesame · clients: admin · slot-alloc · docs
Auth Code + PKCE (S256)"] %% ═══════════ CORE COMMERCE (central hub — top-centre) ═══════════ subgraph CORE["🛒 Core Commerce — App Droplet"] direction TB MED["Medusa v2.12.5 :9000 (PM2)
16 custom modules · 70 workflows
store · /admin · /slots · /b2b APIs"] PG[("Sesame PostgreSQL
DO Managed")] REDIS[("Redis
event bus · workflows
cache · locks · sessions")] VOL["Local volume
uploads"] end CDN["🗄️ DO Spaces CDN
sesame-marketplace-cdn.lon1
static product/marketing images"] RESIZER["🛠️ Image Resizer (utility)
browser convert/resize → upload to CDN"] %% ═══════════ INTEGRATION (DES) ═══════════ subgraph DES["🔀 Data Exchange Service — DES Droplet (stateless)"] direction TB DESS["DES Server :3000 (PM2)
POST /api/webhook · api-key
validate → publish"] RMQ[("RabbitMQ
data-exchange-queue + DLQ
5 retries · exp. backoff")] DESW["DES Worker (PM2)
consume → transform → route"] end %% ═══════════ ERP ═══════════ subgraph ERP["🏢 Odoo ERP — Odoo Droplet"] direction TB ODOO["Odoo 19 (Docker) :8069/:8072
orders · products · customers
fulfilment · QC · barcode picking"] ODOOPG[("Odoo PostgreSQL")] end %% ═══════════ EXTERNAL ═══════════ subgraph EXT["🌐 External Services"] direction TB STRIPE["💳 Stripe"] SENDGRID["✉️ SendGrid"] SMARTAI["🤖 Smart Search AI"] DETRACK["📦 Detrack"] GA["📊 Google Analytics"] end %% ═══════════ INTERNAL TOOLS ═══════════ subgraph TOOLS["🧰 Internal Tools"] direction TB TCMS["🧪 TCMS — Test Case Manager
qa-test-manager · Next.js :3002
own Postgres · ClickUp sync"] end %% ── Lighter note: upcoming / WIP apps ── WIP["🚧 Coming soon / WIP:
Route Planner (in progress) · Sesame 360"]:::wip %% ── Client → app (Buyer AND Producer both use the storefront) ── BUYER -->|opens| SF PRODUCER -->|opens| SF ADMINU -->|opens| ADM LOGU -->|opens| LOG DOCSU -->|opens| DOCS LEADU -->|opens| LAND %% ── Auth: SSO (dashed) vs native ── ADM -. "SSO login-required" .-> SSO LOG -. "SSO login-required" .-> SSO DOCS -. "SSO PKCE (JS adapter)" .-> SSO ADM -->|"token exchange → Medusa JWT"| MED LOG -->|"token exchange → Medusa JWT"| MED SF -->|"buyer & producer login
email/password (native) + REST"| MED LAND -->|"lead form → writes to Sesame DB"| PG %% ── Images served from CDN; resizer publishes to it ── SF -. "loads images" .-> CDN LAND -. "loads images" .-> CDN RESIZER -->|"upload optimized images"| CDN %% ── Core data & storage ── MED --> PG MED --> REDIS MED --> VOL %% ══ SYNC EVENTS (highlighted) ══ %% Direction 1 — Medusa ➜ ERP: CUSTOMER · ORDER · PRODUCER (green) MED ==>|"🟢 SYNC → ERP
CUSTOMER · ORDER · PRODUCER"| DESS DESS ==>|publish| RMQ RMQ ==>|consume| DESW DESW ==>|"ERP REST /json/2 · Bearer"| ODOO %% Direction 2 — ERP ➜ Medusa: PRODUCT (+ fulfillment, marketing) (purple) ODOO ==>|"🟣 SYNC → Medusa
PRODUCT · FULFILLMENT · MARKETING"| DESS DESW ==>|"/admin/* · /dataexchange"| MED ODOO --> ODOOPG %% ── Medusa direct Odoo reads ── MED -. "direct read (POs)" .-> ODOO %% ── Detrack (bidirectional, via Odoo) ── ODOO ==>|"POST /api/job (X-API-KEY)"| DETRACK DETRACK -->|"webhook /api/detrack/job_status
(Basic auth) → relay to Medusa"| ODOO %% ── External APIs ── SF -->|Stripe Elements| STRIPE MED -->|payments · webhooks| STRIPE MED -->|transactional email| SENDGRID MED -->|semantic search| SMARTAI SF -. "page views · events (gtag)" .-> GA %% ── Sync legend ── LEG["🟢 Medusa → ERP: Customer · Order · Producer   |   🟣 ERP → Medusa: Product · Fulfillment · Marketing"] %% Colour the sync edges by direction (green = to ERP, purple = to Medusa) linkStyle 19,20,21,22 stroke:#297948,stroke-width:3px; linkStyle 23,24 stroke:#6B3FA0,stroke-width:3px; %% ── Styling ── classDef sso fill:#EDE7FA,stroke:#6B3FA0,stroke-width:2px; classDef store fill:#E8F0FF,stroke:#1B5FA8; classDef core fill:#FFF3E0,stroke:#C47A10,stroke-width:1.5px; classDef util fill:#EAF5EE,stroke:#297948; classDef wip fill:#F5F0EA,stroke:#B0AAA4,stroke-width:1px,stroke-dasharray:5 4,color:#6B6863; class SSO sso; class PG,REDIS,ODOOPG,RMQ store; class MED core; class RESIZER,CDN util; class TCMS util;
Order-to-ERP Async Flow (Critical Path)
sequenceDiagram participant B as Buyer Browser participant SF as Storefront participant AD as Admin UI participant ME as Medusa participant DES as DES Server participant RMQ as RabbitMQ participant W as DES Worker participant OD as ERP (Odoo) alt Buyer self-service order B->>SF: Place order (Stripe authorize) SF->>ME: POST /store/carts/complete else Admin on-behalf-of order AD->>ME: Create order for customer (/admin, invoice or card) end ME->>ME: Create order + capture payment ME->>DES: POST /webhook action ORDER_CREATED DES->>RMQ: Publish to data-exchange-queue DES-->>ME: 202 Accepted ME-->>SF: Order confirmed SF-->>B: Order confirmation screen Note over RMQ,W: Async - up to 5 retries with exponential backoff, then DLQ RMQ->>W: Deliver message W->>OD: Create sale.order (ERP REST /json/2/, Bearer token) OD-->>W: 200 + ERP record id Note over W,OD: medusa_id stored on the ERP record (no separate crosswalk DB) Note over OD: → continues in the ERP Fulfilment Workflow below
ERP Fulfilment Workflow (Odoo)
flowchart TB SO(["Sales Order
(from Medusa via DES)"]):::start subgraph INBOUND["📥 Inbound — replenish stock"] direction TB PO["Purchase Order
(to supplier)"] RCV["Goods Receipt"] SCAN["Scan (barcode)
+ QC hold"] STORE["Warehouse storage
(put-away · stock on hand)"] PO --> RCV --> SCAN --> STORE end subgraph OUTBOUND["📦 Outbound — fulfil the order"] direction TB PICK["Pick (by location)"] PACK["Pack"] DELIV["Create delivery job
→ Detrack"] VEH["Vehicle / route
allocation (Detrack)"] DUP["Delivery update
(dispatched → delivered)"] PICK --> PACK --> DELIV --> VEH --> DUP end SO --> STORE STORE --> PICK %% ── Fulfilment status sync back to Medusa ── PACK -. "FULFILLMENT packed/shipped → Medusa" .-> MEBACK["↩ Medusa fulfilment sync"]:::sync DUP -. "FULFILLMENT delivered → Medusa" .-> MEBACK %% ── Exceptions ── PACK --> SHORT{"Short-ship?
picked < ordered"} SHORT -- "yes" --> BACK["Backorder shortfall
+ capture/refund difference"]:::exc BACK -. "re-fulfil when in stock" .-> PICK SHORT -- "no" --> DELIV DUP --> RET{"Return / Exchange?"} RET -- "return" --> RRCV["Receive returned goods (QC)
→ refund via Medusa"]:::exc RET -- "exchange" --> REPL["Ship replacement
(new picking)"]:::exc REPL -. "loops back to Pick" .-> PICK classDef start fill:#FFF3E0,stroke:#C47A10,stroke-width:2px; classDef sync fill:#EDE7FA,stroke:#6B3FA0; classDef exc fill:#FCEDED,stroke:#AF3D3D,stroke-dasharray:4 3;
📋 Services Inventory
ServiceStackVersionHostingDeploy TriggerRollback
Medusa Backend Node.js 20 + TypeScript + MikroORM 2.12.5 App Droplet / PM2 :9000 QA: push → develop
Prod: manual workflow_dispatch
✓ workflow
Storefront (simplifab-v2) React 18.3.1 + Vite + Stripe Elements React 18.3.1 App Droplet / Nginx QA: push → develop
Prod: manual
✓ workflow
Admin UI React 18.3.1 + Vite + Medusa UI 4.0.33 React 18.3.1 App Droplet / Nginx QA: push → develop
Prod: manual
✓ workflow
Logistics Dashboard React 18.2.0 + Vite + Zustand + React Query React 18.2.0 App Droplet / Nginx QA: push → develop
Prod: No workflow yet
QA only
Data Exchange Service Node.js 20 + Express + amqplib Express 4.19.2 DES Droplet / PM2 (2 processes) QA: push → develop
Prod: manual
No rollback workflow
Odoo 19 ERP Odoo 19 + PostgreSQL 15 (Docker Compose) 19 Odoo Droplet / Docker push → main (runs deploy script) No rollback workflow
TCMS (QA Tool) Next.js 16.2.4 + Prisma 6.19.0 Next.js 16.2.4 App Droplet (internal) Manual deploy — no automated workflow Internal tool
📨 Message Queue — RabbitMQ
SettingValue
Main queuedata-exchange-queue
Dead letter queuedata-exchange-queue-dlq
Max retries5
Backoff5s, 10s, 20s, 40s, 80s (exponential)
Consumer timeout30 seconds
Max message size1 MB
4xx errors→ DLQ immediately (no retry)
5xx / timeout→ retry up to 5x → DLQ
Event Types Handled (13)
ORDER_CREATED ORDER_UPDATED ORDER_CANCELLED stub PRODUCT_CREATED PRODUCT_UPDATED PRODUCT_DELETED CUSTOMER_CREATED CUSTOMER_UPDATED CUSTOMER_DELETED PRODUCER_CREATED PRODUCER_UPDATED PRODUCER_DELETED FULFILLMENT_UPDATE

🔒 Security Index Checklist

Consolidated security posture and go-live readiness. All pending items must be resolved before launch. A full risk register with SLA calculations will be added post go-live.

43
/ 100
Go-Live Readiness: Not Ready — Blockers Remain
A solid foundation is in place — TLS, RSA-only SSH, isolated VPCs, no public database exposure, JWT auth, and audit logging. However 17 items are pending including critical security gaps, infrastructure tasks, and platform improvements that must be completed before launch.
12
Done
8
High
10
Medium
5
Low
High — Security Risks
  • Enforce secret validation at startup
    JWT_SECRET and COOKIE_SECRET fall back to "supersecret" if env vars are missing. Add a startup check that throws a fatal error if any required secret is absent or still the known default.
  • DigitalOcean Cloud Firewall — all servers, port 443 only public
    All droplets (QA and Prod) must have a DO Cloud Firewall applied: allow inbound 443 from anywhere, allow 22 from trusted IPs only, block everything else. Applies to App Droplet, DES Droplet, and Odoo Droplet in both environments.
  • RabbitMQ management UI locked down (port 15672)
    Port 15672 is publicly reachable without a firewall. Block via DO Cloud Firewall and access only via SSH tunnel: ssh -L 15672:localhost:15672.
  • Block public access to Data Exchange dashboard
    The DES dashboard (dataexchange.sesame.market/dashboard.html) must not be publicly accessible. Restrict via Nginx basic auth or IP allowlist.
  • WAF / DDoS protection via Cloudflare
    Proxy all public domains through Cloudflare (free tier minimum). Enable WAF managed rules and rate limiting on /store/auth, /store/carts/complete, and password-reset endpoints.
  • Stripe webhook signature verification
    Confirm all Stripe webhook endpoints verify the Stripe-Signature header. Unverified webhooks can be spoofed to trigger fraudulent order state changes.
  • DES webhook payload validation
    Confirm the DES action enum rejects all unknown event types to prevent injection via malformed webhook payloads.
  • ORDER_CANCELLED handler — stub not implemented
    When a buyer cancels in Medusa, the DES handler does nothing. Odoo sale order stays "confirmed", causing inventory and billing discrepancies. Implement order.handler.ts:150 before go-live.
Medium — Operational Reliability
  • Verify SSL configuration on all domains
    Confirm Let's Encrypt certificates are active, auto-renewing, and correctly configured on all domains (QA + Prod): storefront, admin, logistics, TCMS, dataexchange, and opshub (Odoo). Check for mixed-content warnings and HSTS headers.
  • Production approval gate for deployments
    Add a GitHub Environment protection rule requiring at least one approver before any production deploy job starts.
  • Uptime monitoring + alerting
    Configure UptimeRobot or DO Monitoring HTTP checks on all public endpoints with Slack/email alerts. Also alert on DLQ size > 0 and disk usage > 80%.
  • Log rotation + off-server log storage
    Install pm2-logrotate (max 50MB, 7 days retention) on all PM2 droplets. Ship logs to Logtail, Papertrail, or S3.
  • Odoo PostgreSQL backup policy
    Odoo DB runs in Docker with a local volume — no automatic backup. Add a daily cron pg_dump + sync to DO Spaces. Enable Droplet-level weekly backup as secondary safety net.
  • Mounted volume backup policy
    Product images and assets are on a DO Volume with no snapshot schedule. Enable DO Volume Snapshots daily or sync to Spaces via cron.
  • ~ Webhook retry / circuit breaker (Medusa → DES)
    Medusa fires a single HTTP POST with no retry. If DES is down, events are silently lost. Add retry with exponential backoff to webhook-service.ts.
  • ~ Production branch protection
    develop is protected with peer review. main has no approval gate. Add GitHub Environment protection rules.
  • ~ Odoo deployment — no QA gate
    Odoo deploys on push to main. Should follow feature → develop → main with a QA Odoo environment before production changes land.
  • ~ DES rollback workflow
    Rollback workflows exist for Medusa, Admin, and Storefront, but not DES or Odoo. Create rollback YMLs before go-live.
Low — Improvements
  • ~ QA / Prod service version parity
    Confirm QA and Production run identical versions of all service modules. Streamline so QA always mirrors what will be promoted to Prod.
  • ~ Encryption at rest — verify all volumes
    DO Managed PostgreSQL encrypts at rest by default. Verify the Odoo Droplet's local PostgreSQL Docker volume and the mounted assets volume.
  • Logistics Dashboard production deploy workflow
    No production deploy or rollback workflow exists. Create before go-live to avoid manual SSH deploys under pressure.
  • SonarQube & Snyk — SAST and dependency scanning
    Neither tool is integrated yet. SonarQube provides static code analysis and security hotspot detection on every PR. Snyk scans npm dependency trees and the Odoo Docker image for known CVEs. See CI/CD tab for the full integration plan.
  • Google Analytics integration
    GA4 not yet integrated on storefront or admin UI. Add before go-live so day-one traffic is tracked.
Controls In Place
  • TLS on all public endpoints — Let's Encrypt, auto-renewed via certbot, HTTPS-only on port 443.
  • RSA-key SSH only — Password authentication disabled on all droplets.
  • Database not publicly accessible — PostgreSQL accessible only within VPC private network.
  • Separated environments — Production and QA on separate VPCs, no cross-environment traffic.
  • API key auth on DES — Webhook endpoints require X-API-Key. Admin endpoints require Bearer token.
  • JWT + actor-based auth on Medusa — Separate auth actors (store, admin, logistics) with scoped tokens.
  • Secrets injected at deploy time only — GitHub Actions Secrets used; .env files never committed.
  • No PII in logs — Logging standards prohibit email, phone, address, card data, and tokens in all logs.
  • Audit log module — Database-level audit triggers on all Medusa tables.
  • Stripe authorize-only pattern — Payments authorised at checkout, captured post-order only.
  • Daily DB backup (Prod) — DO Managed PostgreSQL automatic daily backups, 7-day retention.
  • RBAC roles — Three logistics roles with scoped permissions enforced at API level.
🔑 Authentication Mechanisms
SurfaceMethodActorsStatus
Medusa Store APIJWT (emailpass actor)Buyers, ProducersIn place
Medusa Admin APIJWT (emailpass actor)Admin usersIn place
Medusa Logistics APIJWT (logistics actor)Logistics agents/managersIn place
DES Webhook endpointAPI key (X-API-Key header)Medusa backend onlyIn place
DES Admin endpointsBearer tokenInternal adminIn place
Odoo APIBearer token (Odoo API key)DES WorkerIn place
GitHub Actions → ServersRSA SSH key (deploy key)CI/CD onlyIn place
Production deploy approvalGitHub Environment protectionSenior devNot configured
ℹ️ A full risk register with SLA targets, owner assignments, and mitigation timelines will be added after go-live.

🚀 CI/CD & Operations

GitHub Actions-based CI/CD with Capistrano-style symlink deploys. QA deploys automatically on push to develop. Production deploys are manual (workflow_dispatch). All deployments include automatic rollback on failure and keep the last 5–10 releases.

🌿 Branching Strategy

Full branching strategy including hotfix flow is documented in the Developer Guide → Branching Strategy section.

⚙️ Deployment Pipelines by Service
Medusa Backend
push → develop / workflow_dispatch
npm ci → medusa build → tar.gz
SCP to server → extract → symlink → PM2 restart
medusa db:migrate → health check
🟢 merge develop→main + release tag (prod only)
ℹ️ Keeps last 10 releases. Rollback via rollback-production.yml — accepts specific release timestamp or defaults to previous.
Static Frontends (Storefront, Admin UI, Logistics)
push → develop / workflow_dispatch
npm ci → vite build (env vars injected)
rsync dist/ → server → symlink
nginx reload → file health check
⚠️ Logistics Dashboard has no production workflow yet. Must be created before go-live.
Data Exchange Service
push → develop / workflow_dispatch
npm ci → tsc build
rsync dist/ → server → .env symlink → npm ci --omit=dev
PM2 via ecosystem.config.js → verify server + worker
⚠️ No rollback workflow for DES. If a bad deploy goes out, manual SSH intervention is needed.
Odoo ERP
push → main
SSH → /opt/client_deploy_odoo.sh
⚠️ Deploy triggers on push to main (no QA environment for Odoo). No rollback workflow. Branching strategy not yet standardised.
🔍 Code Quality & Security Scanning — Planned Integration
ℹ️
SonarQube and Snyk are planned additions to the CI pipeline. Neither is active yet. Once integrated, they become mandatory gates — a PR cannot merge to develop if either tool reports a blocker.
SonarQube — Code Quality & SAST
Static Application Security Testing + code quality analysis
AspectDetail
What it catchesCode smells, duplications, complexity, SQL injection, XSS, hardcoded secrets, insecure patterns
Applies toMedusa backend, DES, TCMS (TypeScript/Node.js repos)
Hosting optionSonarCloud (SaaS, free for public repos) or self-hosted SonarQube Community on a droplet
CI triggerOn every PR to develop — blocks merge if Quality Gate fails
Quality GateCoverage > 70%, 0 blocker issues, 0 critical security hotspots unreviewed
GitHub integrationPR decoration — inline comments on offending lines
StatusNot yet integrated
INTEGRATION STEPS
  1. Create SonarCloud org linked to GitHub org (or deploy SonarQube Community to a DO Droplet)
  2. Add sonar-project.properties to each repo root
  3. Add SONAR_TOKEN to GitHub Actions secrets
  4. Add a sonar-scan.yml workflow step that runs before the build step on PRs to develop
  5. Configure branch analysis for main and develop
  6. Set Quality Gate thresholds and enforce PR blocking via GitHub branch protection
Snyk — Dependency & Container Security
Open source vulnerability scanning for npm dependencies and Docker images
AspectDetail
What it catchesKnown CVEs in npm dependencies, outdated packages with exploits, Docker image vulnerabilities, license compliance issues
Applies toAll repos with package.json + Odoo Docker image
HostingSnyk SaaS — free tier covers open source scanning
CI triggerOn every PR + nightly scheduled scan on main
Block thresholdCritical and High severity CVEs with a fix available block the PR
Container scanScan odoo:19 Docker image for OS-level CVEs on each Odoo deploy
StatusNot yet integrated
INTEGRATION STEPS
  1. Create Snyk account, link to GitHub org — Snyk auto-imports all repos
  2. Add SNYK_TOKEN to GitHub Actions secrets
  3. Add snyk test step to each repo's PR workflow (runs after npm ci)
  4. Add snyk container test odoo:19 to the Odoo deploy workflow
  5. Configure Snyk PR checks in GitHub to block merge on critical CVEs
  6. Enable Snyk nightly monitor on main for new vulnerability disclosures
TARGET CI PIPELINE WITH BOTH TOOLS (per PR to develop)
PR opened to develop
npm ci
Snyk test — dependency CVE scan
SonarQube scan — SAST + quality gate
npm test — unit + integration tests
All gates pass — PR can be merged
🔄 Process Management (PM2)
PM2 ProcessDropletScriptWorking DirPortRestarts
medusa-backendApp Dropletnpm run start/home/deploy/backend/current/.medusa/server9000On crash
data-exchange-serverDES Dropletdist/server.js/home/deploy/des/current3000On crash
data-exchange-workerDES Dropletdist/worker.js/home/deploy/des/currentOn crash
📊 Observability Status
  • Structured logging — Winston logger, all services. PII-safe. Log levels: debug/info/warn/error.
  • Audit log — Database-level trigger on all Medusa tables.
  • Log rotation — Not configured. PM2 logs write unbounded to disk.
    Action: configure pm2-logrotate + ship to external store.
  • Uptime monitoring — Not implemented.
    Action: UptimeRobot or DO Monitoring for all public endpoints.
  • DLQ alerting — No alerts when messages land in the dead letter queue.
    Action: poll RabbitMQ management API, alert on DLQ size > 0.
  • Error alerting — No Slack/email alerts on service errors.
💾 Backup & Recovery
AssetEnvMethodRetention
PostgreSQL (Medusa)ProdDO Managed — automatic7 days
PostgreSQL (Odoo)ProdTODOTODO
Mounted volume (assets)ProdTODOTODO
RTO (recovery time)ProdTODO
RPO (data loss tolerance)ProdTODO
🧪 Testing Strategy
Unit Tests
Jest 29.7.0
Medusa backend + DES
Run: npm test
Integration Tests
Jest HTTP integration
Medusa backend
Real DB required
E2E Tests
Playwright baseline packs
Managed via simplifab-tcms
CI automation via POST /api/automation/results

📦 Major Modules, Frameworks & Upgrade Paths

Key framework versions, the extent of our customisations on each, and what it takes to upgrade. Medusa and Odoo are the two highest-risk upgrades due to the depth of customisation.

🔢 Framework & Dependency Versions
Medusa2.12.5 Odoo19 (Docker) Node.js≥ 20 TypeScript5.6.2 MikroORM6.4.16 React (most frontends)18.3.1 React (TCMS)19.2.4 Next.js (TCMS)16.2.4 Vite5.4.x / 6.0.x Prisma6.19.0 Express4.19.2 amqplib0.10.4 PostgreSQL15 Stripe (React)3.9.0 / 7.8.0 Tailwind CSS3.4.x / 4.2.x
🟣 Medusa — Upgrade Complexity & Process
⚠️
HIGH RISK. We have 13 custom modules built on top of Medusa's internal APIs, module system, and MikroORM. Any Medusa upgrade can introduce breaking changes to the module registration API, entity schema, or workflow engine. Previous upgrade (patch/minor) took 2–5 days with 2 developers.
13 Custom Modules
ModuleRisk
logistics-userMedium
logistics-slotMedium
organizationHigh
order-setHigh
recurring-orderHigh — scheduler + workflow
split-order-paymentHigh — payment logic
audit-logHigh — DB triggers, must load last
product (custom)Medium
briefsLow
favoritesLow
supplier_preferenceLow
buyer_preferenceLow
Upgrade Checklist
  • Read official Medusa v2 changelog + migration guide
  • Check for breaking changes in module registration API
  • Check for breaking changes in MikroORM version (bundled)
  • Update all @medusajs/* packages to same version (all are pinned together at 2.12.5)
  • Run medusa db:migrate on QA — review generated migration files
  • Test each of the 13 custom modules on QA
  • Validate audit-log triggers survive the migration
  • Update Admin UI — uses @medusajs/ui + @medusajs/js-sdk which must match backend version
  • Run full Playwright E2E suite on QA
  • 2-developer, 2–5 day estimate for patch/minor upgrades
  • Budget more for major version upgrades
🟢 Odoo — Upgrade Complexity & Process
⚠️
HIGH RISK. Odoo major version upgrades (e.g. 19 → 20) require running Odoo's official upgrade script, manual module migration, and database schema migration. Our 4 custom modules must be updated to the new version's API. Docker image-based deployment simplifies running the upgrade but does not reduce the code migration effort.
4 Custom Odoo Modules
ModuleDependenciesRisk
zt_medusa_product product, stock, zt_medusa_customer_producer, account High
zt_medusa_order sale, product, contacts, purchase, stock, account High
zt_medusa_customer_producer contacts, base Medium
detrack_shipping_integration delivery, stock, stock_delivery Medium
Upgrade Process (Docker-based)
  • Take full PostgreSQL dump of Odoo DB before any upgrade
  • Update Docker image tag in docker-compose.yml
  • Run Odoo's official upgrade script: odoo --update=all
  • Update each custom module for new version API (view XML, model fields, Python API changes)
  • Test all 4 custom modules thoroughly on QA environment
  • Validate DES integration — verify JSON-RPC field names haven't changed
  • For major version upgrades: use Odoo's SaaS upgrade service or migration scripts
  • Standardise Odoo branching (feature → develop → main) before next upgrade
📌 Other Dependencies — Upgrade Notes
PackageCurrentUpgrade EffortNotes
Node.js≥ 20 Low Follow Medusa's Node.js requirement. Test PM2 compatibility.
MikroORM6.4.16 Medium Bundled with Medusa. Only upgrade as part of Medusa upgrade, not independently.
Stripe SDK3.9.0 / 7.8.0 Low Follow Stripe deprecation notices. Test payment flow on QA after any upgrade.
React (frontends)18.3.1 Medium React 19 migration requires testing all component libraries (Radix UI, Medusa UI, shadcn). TCMS already on React 19.
Prisma (TCMS)6.19.0 Low Internal tool. Minor version updates safe; run prisma migrate deploy on upgrade.
amqplib0.10.4 Low Stable library. Test DES queue consumer/publisher after any upgrade.
PostgreSQL15 (Odoo Docker) High PostgreSQL major version upgrade requires pg_upgrade. Coordinate with Odoo upgrade. DO Managed PG upgrades are handled by DigitalOcean.

🖥️ Servers & Sizing

All droplets are hosted on DigitalOcean London 1 (LON1). Production and QA run in separate VPCs. Data sourced from DigitalOcean API — last synced 2026-04-26.

🟢 Production Droplets
Name vCPU RAM Disk OS Public IPs Private IP Price Status Created
prod-medusa 4 8 GB 160 GiB + volume Ubuntu 25.10 138.68.172.146
157.245.30.240
10.106.16.2 $48 / mo active 2025-11-09
prod-odoo 2 4 GB 80 GiB Ubuntu 24.04 LTS 138.68.179.182
129.212.163.204
10.106.16.4 $24 / mo active 2026-02-27
prod-data-exchange-service 1 2 GB 50 GiB Ubuntu 24.04 LTS 167.71.140.205 10.106.16.5 $12 / mo active 2026-04-19
Production VPC: 0c3c8352-9f6f-483e-9745-aad7d9c65b95  ·  Mounted volume (prod-medusa): 6ea6b602-bd5b-11f0-9ecf-0a58ac12d651  ·  Total: $84 / mo
🔵 QA Droplets
Name vCPU RAM Disk OS Public IPs Private IP Price Status Created
qa-medusa 2 4 GB 80 GiB Ubuntu 25.04 138.68.190.199
188.166.137.177
10.106.0.2 $24 / mo active 2025-10-07
qa-odoo 2 4 GB 80 GiB Ubuntu 24.04 LTS 206.189.118.155
157.245.31.205
10.106.0.3 $24 / mo active 2025-12-17
qa-data-exchange-service 2 4 GB 80 GiB Ubuntu 24.04 LTS 178.62.96.51
129.212.202.28
10.106.0.4 $24 / mo active 2025-12-31
QA VPC: d44d5b2d-9194-466d-b637-e4582e6fd219  ·  Total: $72 / mo
🔐 Common Droplet Features
  • Monitoring agent — enabled on all droplets (droplet_agent)
  • Private networking — all droplets on VPC private network
  • SSH access — RSA key-based only; password auth disabled
  • Backups — enabled on prod-medusa, prod-odoo, prod-data-exchange-service
  • Backupsnot enabled on qa-odoo, qa-data-exchange-service
  • IPv6 — enabled on prod-data-exchange-service; optional on others
💰 Cost Summary
EnvironmentDroplets$/mo
Production3 droplets$84
QA3 droplets$72
DO Managed PostgreSQLnot tracked here
Droplets total$156 / mo
⚠️ OS Version Notes
  • qa-medusa is running Ubuntu 25.04 — this image is marked retired by DigitalOcean. Should be upgraded or rebuilt on 24.04 LTS or 25.10.
  • prod-medusa runs Ubuntu 25.10 (latest base image available at creation time).
  • prod-odoo, qa-odoo, prod-des, qa-des all run Ubuntu 24.04 LTS — stable and supported.
🗄️ Managed Databases (DigitalOcean)
Name Engine Version Size Nodes Storage Databases Used by VPC Maintenance Status Created
sesame-prod-pg-db PostgreSQL 17 db-s-1vcpu-1gb 1 10 GiB defaultdb, simplifab_prod Medusa, TCMS Production VPC Tuesday 21:07 UTC online 2025-11-09
db-postgresql-odoo PostgreSQL 16 db-s-1vcpu-1gb 1 10 GiB defaultdb, odoo_erp Odoo 19 Production VPC Saturday 12:02 UTC online 2026-04-21
sesame-prod-pg-db
Host: sesame-prod-pg-db-do-user-26720238-0.j.db.ondigitalocean.com
Private host: private-sesame-prod-pg-db-do-user-26720238-0.j.db.ondigitalocean.com
Port: 25060 · SSL required · Users: doadmin (primary)
db-postgresql-odoo
Host: db-postgresql-odoo-do-user-26720238-0.d.db.ondigitalocean.com
Private host: private-db-postgresql-odoo-do-user-26720238-0.d.db.ondigitalocean.com
Port: 25060 · SSL required · Users: doadmin (primary), app_user
⚠️ Both databases have a pending maintenance window — updates have not yet been applied. Both are single-node (no standby replica) — a maintenance event will cause a brief outage.
💾 Block Storage Volumes
ID Attached to Environment Purpose
6ea6b602-bd5b-11f0-9ecf-0a58ac12d651 prod-medusa Production Application data, uploaded images & assets
⚠️ Volume name, size, mount path, and filesystem type are not yet captured. Run the command below and share the output to complete this section:
doctl compute volume list --output json

💻 Local Development Setup

Step-by-step guide for getting the Sesame platform running on a local machine. Content to be added.

Documentation for local development setup will be added here.

🌿 Branching Strategy

Git branching model for all Sesame / Simplifab repositories. QA deploys automatically on merge to develop. Production deploys are manual (workflow_dispatch) and merge develop → main on success.

Branch Flow
gitGraph LR: commit id: "initial" branch develop checkout develop commit id: "dev work" branch feature/abc checkout feature/abc commit id: "feat: build abc" commit id: "feat: tests" checkout develop merge feature/abc id: "PR merged → QA deploy" branch feature/xyz checkout feature/xyz commit id: "feat: build xyz" checkout develop merge feature/xyz id: "PR merged → QA deploy 2" checkout main merge develop id: "workflow_dispatch → Prod deploy" branch hotfix/issue-99 checkout hotfix/issue-99 commit id: "fix: critical bug" checkout main merge hotfix/issue-99 id: "hotfix → main" checkout develop merge hotfix/issue-99 id: "hotfix → develop"
🔀 Normal Feature Flow
  1. Cut feature/xxx from develop
  2. Build and commit on the feature branch
  3. Raise a PR to merge into develop
  4. Peer review required — no self-merge
  5. On merge → QA deploy triggers automatically
  6. When QA is signed off → trigger prod deploy via workflow_dispatch
  7. Successful prod deploy auto-merges develop → main and creates a release tag
🚑 Hotfix Flow
  1. Cut hotfix/xxx directly from main
  2. Apply the fix and commit
  3. Merge hotfix/xxx → main (triggers prod deploy)
  4. Sync the fix back: merge hotfix/xxx → develop
  5. Delete the hotfix branch after both merges
Hotfix branches are cut from main, not develop, so production gets only the minimal fix without picking up unreleased QA work.
📏 Branch Rules
  • main — production-ready only, protected
  • develop — protected, peer review required
  • feature/xxx — branch from develop
  • hotfix/xxx — branch from main
  • No direct commits to main or develop
  • Commit messages must reference issue number
  • No qa branch — QA environment = develop
⚠️ Exceptions & Known Gaps
  • Odoo — branching not yet streamlined; deploy still runs directly from main via shell script.
  • TCMS — no deploy workflow yet; deployed manually.
  • Logistics Dashboard — no production deploy workflow; QA only.

📋 Logging Standards

Mandatory logging rules for all Sesame / Simplifab services. These standards must be followed before merging to any release branch. Source of truth: simplifab-ecom-server/LOGGING.md.

⚙️ Configuration
EnvironmentLOG_LEVELFormatOutput
DevelopmentdebugHuman-readable, no timestampConsole only
ProductioninfoJSON + timestampFile (LOG_FILE env var)
LOG LEVELS (low → high)
debug — dev only info — operational warn — needs attention error — failures
🚫 Never Log — PII & Secrets
  • Full email addresses (use masked: j***@domain.com)
  • Phone numbers or physical addresses
  • Credit card numbers (even partial)
  • Passwords, tokens, API keys, session IDs
  • Social security numbers, birth dates
  • Bank account information
📏 The 13 Rules
Rule 1 — Structured logging
Always pass metadata as an object (second param). Never concatenate strings.
logger.info("Order created", { orderId, total })
Rule 2 — Correct log levels
debug for dev detail · info for normal ops · warn for attention needed · error for failures
Rule 3 — LOG_PREFIX in every file
Declare const LOG_PREFIX = "ClassName" at the top of every file. Prefix every log message.
logger.info(`${LOG_PREFIX}: Request received`, { orderId })
Rule 4 — Never log PII or secrets
See list above. Use sanitizeEmail() helper for email addresses.
Rule 5 — Log every incoming request
Before processing starts, log essential request info on every API endpoint.
Rule 6 — Log all external API calls with timing
Every call to Stripe, SendGrid, Detrack, S3, Cloudflare AI, Odoo — log it with duration.
Rule 7 — Log response status on every API call
After processing completes, log the outcome with relevant context.
Rule 8 — Always log errors in catch blocks
Every catch block must log error.message + error.stack. Re-throw after logging.
Rule 9 — Log only required fields
Never log entire objects. Pick specific fields. No console.log — use logger only.
Rule 10 — Remove debug logs before committing
No TEMP:, TODO:, or console.log commits. Permanent logs only.
Rule 11 — No emojis in logs
Emojis break log aggregation tools. Plain text only.
✗ "✅ Order created"   ✓ "Order created"
Rule 12 — Log auth & authz events
Log successful logins, failed logins (with sanitizeEmail), and all unauthorised access attempts.
Rule 13 — Log security-critical events
Role changes, data access/modification, configuration changes — all must be logged with actor ID.

🛍️ Store Front

Sesame is a B2B food wholesale marketplace connecting food businesses with vetted producers and wholesalers. The storefront is the customer-facing web application — the primary interface for both Buyers and Producers on the platform.

🌐 What is the Store Front?

The Store Front (simplifab-v2) is the main web application that buyers and producers interact with daily. It is a role-based platform — the experience differs significantly depending on whether you are logged in as a Buyer or a Producer. There is also a public-facing marketing layer (home page, how it works, producer profiles) accessible without an account.

All meaningful commerce functionality sits behind authentication. Account creation requires admin approval — see the Onboarding Process playbook for how users get access.

👤 Buyers (Customers)

Food businesses — restaurants, retailers, caterers — who source products through the platform.

  • Browse and search a food product catalogue organised by category
  • View detailed product and producer profile pages
  • Build a cart and check out with Stripe payments
  • Track upcoming and past orders
  • Save favourite products and producers
  • Request product samples from producers
  • Create sourcing briefs to invite offers from suppliers
  • Manage account settings and team members
🏭 Producers (Suppliers)

Food producers and wholesalers who list and fulfil orders through the platform.

  • View and manage incoming purchase orders
  • Generate pick notes as PDFs
  • Add and edit product listings
  • View sales analytics and order history
  • Manage shop profile and account settings
🗺️ Main Areas of the Application
AreaWho uses itWhat it covers
Public / MarketingAnyoneHome page, how it works, contact, terms, public producer profiles — no login required
Registration & AuthNew applicantsBuyer and producer registration, email verification, password setup and reset, team invitations
Buyer PortalBuyersDashboard, product search & browse, product detail, cart, checkout, order tracking, favourites, sourcing briefs, account settings
Producer PortalProducersOrder / purchase order management, product management, analytics, settings
Internal / OpsInternal teamLogistics dashboard, operations dashboard, warehouse receipt confirmation tool

🚀 Onboarding Process

End-to-end guide for how Customers (Buyers) and Producers (Suppliers) are onboarded onto the Sesame platform. This guide is intended for the business and tech support team — not a developer reference.

ℹ️
Two account types, one process pattern. Both Customers and Producers go through the same high-level steps: submit an application → admin reviews → receive a setup email → activate account with a password. No password is collected at registration time. Accounts are not active until an admin approves them.
📋 Onboarding Timeline — Both Account Types
1. Applicant fills registration form
2. Application submitted (no account yet)
3. Admin reviews & approves in backend
4. Welcome email sent with account setup link
5. Applicant clicks link, sets password
🟢 Account active — user can log in
📝 Step 1 — Customer Fills the Registration Form

The customer navigates to /register/buyer on the Sesame storefront. They complete the following fields:

Personal Information
FieldRequiredNotes
First NameYesLetters, hyphens and apostrophes only
Last NameYesLetters, hyphens and apostrophes only
Email AddressYesValidated for uniqueness in real time — duplicate emails are rejected immediately
Phone NumberYesCountry code + number (supports +44, +1, +33, +49). Validated for uniqueness in real time
Role / DesignationYesFree text, e.g. "Head Chef", "Procurement Manager"
Company NameYesTrading name of the buyer's business
Delivery Address (optional)
FieldRequiredNotes
Address Line 1NoHelps estimate delivery options
Address Line 2No
CountyNoUK searchable select
CityNo
PostcodeNoAuto-uppercased
Other Fields
FieldRequired
How did you hear about Sesame?No
Agree to Terms & ConditionsYes
Marketing ConsentNo
⚠️ No password is collected at this stage. The customer is not creating a login account yet — they are submitting an application. The account is only activated after admin approval.
Step 2 — Application Submitted

On successful submission the customer is taken to an Application Under Review confirmation page. They see a three-stage timeline:

  • Set up your account now — the customer will receive a setup email once approved.
  • Review within 24 hours — the admin team will review the application.
  • Full access once approved — the buyer dashboard unlocks after activation.

The application is stored in the backend. No email is sent to the customer at this point — the email only goes out after admin approval.

🔐 Step 3 — Admin Reviews and Approves

The admin team reviews the application in the Sesame Admin panel. Until the admin approves:

  • The customer cannot log in — they will see an "Application Under Review — pending approval" message if they try.
  • No welcome/setup email has been sent yet.
ℹ️ If the application is rejected, the customer will see a "has been rejected" message when attempting to log in. No automated rejection email is sent by the storefront — the admin team should communicate rejection separately if required.
📧 Step 4 — Welcome Email & Account Setup

Once the admin approves the application, the platform automatically sends the customer a welcome email containing a unique account setup link.

DetailValue
Link destination/setup-account?token=<unique_token>
What the link doesTakes the customer to a page where they set their password for the first time
Token expiryTokens expire — if the customer does not use the link in time they must request a new one (see Password Reset section below)
Information shown on setup pageOrganisation name and email address are pre-filled and read-only; only password and confirm password are entered
⚠️ Support scenario: If a customer says they never received the setup email, or the link has expired, use the Resend Setup Email process described in the Password & Account Recovery section below.
🟢 Step 5 — Account Activated

After the customer sets their password:

  • The account is immediately active.
  • The customer is automatically logged in and redirected to the Buyer Dashboard after 1.5 seconds.
  • They can now log in at any time via /login using their email and the password they just set.
📝 Step 1 — Producer Fills the Registration Form

The producer navigates to /register/supplier on the Sesame storefront. The form has more fields than the customer form because the platform requires verified business information for producers.

Personal Information
FieldRequiredNotes
First NameYesLetters, hyphens and apostrophes only
Last NameYesLetters, hyphens and apostrophes only
Email AddressYesPersonal email; validated for uniqueness in real time
Phone NumberYesCountry code + number; validated for uniqueness
Role / DesignationYese.g. "CEO", "Managing Director", "Head Chef"
Business Information
FieldRequiredNotes
Legal Entity / Business NameYesValidated for uniqueness in real time
Business EmailNoIf different from personal email; must not match personal email
Business Registration NumberYes8 alphanumeric characters; validated for uniqueness
Year FoundedYes4-digit year between 1800 and current year
Size of BusinessYes1–5 / 6–20 / 21–50 / 51+ employees
Business TypeYesRestaurant, Cafe, Bar, Retailer, Caterer, Wholesaler, Manufacturer, or Other
Business DescriptionYes20–500 characters
Website / Social MediaNoCan tick "My business does not have a website"
Business AddressNoLine 1, Line 2, County, City, Postcode
Final Section
FieldRequired
How did you hear about Sesame?No
Agree to Terms & ConditionsYes
Marketing ConsentNo
⚠️ No password is collected at this stage. Same as the customer flow — the producer is submitting an application only. The account is only activated after admin approval.
Step 2 — Application Submitted

On successful submission the producer is taken to an Application Under Review page, identical in structure to the customer version. The same three-stage timeline is shown. No email is sent at this point.

🔐 Step 3 — Admin Reviews and Approves

Identical to the customer flow. The admin reviews the producer's business details in the Admin panel. Until approved, the producer cannot log in and will see the "pending approval" message if they try.

ℹ️ Producer applications typically carry more scrutiny than buyer applications — the business registration number, legal entity name, and business description are all key verification points for the admin review.
📧 Step 4 — Welcome Email & Account Setup

Identical to the customer flow. Once the admin approves, the platform sends a welcome email with a unique setup link to the producer's personal email address. The producer clicks the link, sets their password, and the account is activated.

🟢 Step 5 — Account Activated

After setting their password, the producer is automatically logged in and redirected to the Supplier Dashboard. They can subsequently log in at any time via /login.

⚖️ Customer vs Producer — Key Differences
AspectCustomer (Buyer)Producer (Supplier)
Registration URL/register/buyer/register/supplier
Form complexityShorter — personal info + company name + optional addressLonger — full business details including registration number, year founded, size, type, description
Business registration numberNot collectedRequired — 8 alphanumeric characters, must be unique
Separate business emailNot collectedOptional field (must differ from personal email)
Post-approval dashboardBuyer Dashboard (/buyer/dashboard)Supplier Dashboard (/supplier/dashboard)
Welcome email triggerSame — sent on admin approvalSame — sent on admin approval
Account setup link URL/setup-account?token=… — same/setup-account?token=… — same
Password & Account Recovery
🔑 Forgotten Password / Resend Setup Email

This single flow handles two situations: a user who has forgotten their password, and a user who never activated their account (e.g. the setup link expired before they clicked it). Both are handled via /forgot-password.

User visits /forgot-password
Enters their email address
Platform sends appropriate email (setup or reset)
User clicks link → visits /reset-password?token=…
User enters new password → account accessible
ℹ️
How it works behind the scenes: When an email is submitted on the forgot-password page, the platform fires two requests simultaneously — one for accounts that were approved but never set a password, and one for fully active accounts. Exactly one of these will produce an email; the platform always shows a "check your email" message regardless, so no information about whether an account exists is leaked.
Reset Link Behaviour
ScenarioWhat happens
Valid token, user visits /reset-password?token=…Sees the new-password form with their email pre-shown (read-only)
Expired or invalid tokenSees an error card with a "Request New Reset Link" button — clicking it returns them to /forgot-password
User successfully sets new passwordSees a "Password Reset Complete" confirmation and a Sign In button
🔄 Changing Password (Logged-in User)

A logged-in user can change their password from their account settings. The path depends on account type:

Account typeSettings path
Customer (Buyer)/buyer/settings/password
Producer (Supplier)/supplier/settings/password
User enters current password
Platform verifies current password & sends reset email
User receives email (valid for 15 minutes)
User clicks link → sets new password via /reset-password?token=…
⚠️ The change-password link expires in 15 minutes. If the user misses it, they can use the "Didn't receive it? Resend" option on the confirmation screen, which re-sends the link.
Common Support Scenarios
🛟 Support Reference — What to Do When
User reportsLikely causeAction
"I submitted my application but can't log in" Application is still pending admin approval Check the Admin panel — if the application hasn't been reviewed, approve or reject it. Remind the user that approval takes up to 24 hours.
"I never received a setup email" Admin may not have approved yet, or the email went to spam Confirm the application is approved in the Admin panel. Ask user to check spam. If approved and no email received, use Resend Setup Email (the user visits /forgot-password and enters their email).
"My setup / activation link says it's expired or invalid" The welcome link has a token expiry Direct the user to /forgot-password. They enter their email and a fresh setup link is sent.
"I forgot my password" Standard forgotten password Direct the user to /forgot-password. They enter their email and receive a reset link.
"I got a password reset email I didn't request" Someone else entered their email on the forgot-password page Reassure the user — if they did not click the link, nothing changes. The link expires. No action needed unless they suspect their account is being targeted.
"My reset link has expired" User took longer than the token lifetime to click the link Direct the user to /forgot-password again to request a new link.
"I see 'Your application has been rejected'" Admin has rejected the application Review the rejection reason in the Admin panel. Contact the user directly to explain — the platform does not send an automated rejection email.
"I see 'Your account is not active'" Account was deactivated after creation Check account status in the Admin panel and reactivate if appropriate.

Admin Review Checklist

Step-by-step guide for the admin team when reviewing and approving a new application in the Admin panel. Customer (Buyer) and Producer (Supplier) applications have different requirements — this checklist covers both, with the customer-specific steps called out clearly.

🗂️ Where to Find Applications

All incoming applications are found under the Applications section in the Admin panel. There are three views:

ViewWhat it showsWhen to use
All Applications Every application — filterable by status (Pending, Approved, Rejected) Day-to-day review queue
Pending Setup Applications that were approved but the platform account was not fully created yet Use if a user was approved but says they never got a setup email — check here first
Rejected All rejected applicants and their rejection reasons Audit trail; reference if a rejected applicant re-applies or follows up
ℹ️ Click any row to open the application detail page. The Edit button at the top right opens the Review Application drawer where you take action.
🔍 What to Review Before Approving or Rejecting

The application detail page shows four sections. Review all of them before making a decision.

SectionKey things to check
General Organisation name, account type (Buyer / Supplier), referral source, current status
Contact Primary contact name, email address, phone number, job title — verify these look legitimate
Address Registration address — check it aligns with the business type and UK geography
Delivery Addresses Delivery location(s) the applicant provided — relevant for logistics planning
Producer-only fields to verify
  • Business Registration Number (8 alphanumeric chars — verify against Companies House if needed)
  • Year Founded
  • Business Type and Size
  • Business Description (20–500 chars — should be coherent)
  • Website / Social media (cross-check legitimacy)
⚖️ Taking Action — Approve or Reject

Click Edit on the application detail page to open the Review Application drawer. The first decision is whether to approve or reject.

Approving
  • Set Status to Approved
  • For customers: complete the three additional steps below before saving
  • For producers: set the Acknowledgement Required flag if needed (see below)
  • Click Save — the platform sends the welcome email automatically
Rejecting
  • Set Status to Rejected
  • Enter a Rejection Reason (minimum 10 characters — required)
  • This reason is stored internally; no automated rejection email is sent to the applicant
  • The team should contact the applicant separately if needed
  • The applicant will see "has been rejected" if they attempt to log in
⚠️ Once approved, the welcome email is sent immediately. Make sure you have completed all the customer-specific steps (Customer Group and Credits) before clicking Save on an approval — these cannot be set during the save dialog after the fact and must be revisited separately.
Customer (Buyer) — Additional Steps at Approval
ℹ️ The following two steps only appear in the Review Application drawer when approving a Customer (Buyer) account. They do not apply to Producer applications.
👥 Customer Group Assignment

The Customer Group field determines which price list the customer will see across the platform. Every price list is linked to one or more customer groups — so assigning a customer to a group effectively sets their pricing tier.

OptionWhat happens
Select a specific Customer Group The customer is added to that group and the price list linked to it applies to them
Leave blank The platform automatically assigns the Standard price list — use this for most new buyers unless a specific pricing arrangement has been agreed
⚠️ Check with the commercial team if you are unsure which group a customer should be in. Assigning the wrong group gives the customer incorrect pricing.
How the Group → Price List chain works
Customer assigned to a Group
Group is linked to a Price List
Price List overrides apply when customer shops
No group = Standard price list applied automatically
Changing a customer's group after approval

You can reassign a customer's group at any time from the Customer detail page in the Admin panel (not the Application page). Navigate to Customers → find the customer → use the Customer Groups section to add or remove groups.

💳 Credit Account Setup

The credit feature allows selected customers to purchase on credit up to a defined limit, rather than paying at checkout immediately. This is an optional, opt-in feature — most customers will not have it enabled at onboarding.

When Approving (in the Review drawer)
  • Allow Credit checkbox — tick this only if the customer has been approved for credit terms by the finance team
  • Credit Limit (£) — enter the agreed credit limit in GBP. Only shown when Allow Credit is ticked
  • Credit Limit Note — explain why this credit limit was set (e.g. "Onboarded with £2,000 credit — approved by finance on [date]"). This is required whenever a credit limit is entered and forms part of the audit trail
After Approval (on Customer or Application detail)
  • Enable Credit button — appears in the Credit section of the customer's detail page if credit is not yet active
  • Change Limit — adjusts the credit limit. A reason note is required whenever the amount changes
  • Disable Credit — immediately deactivates credit for the customer. No note required
  • View History — shows a full audit log of all credit limit changes, including who made the change, the old and new amounts, and the reason provided
⚠️ A reason note is mandatory every time the credit limit value changes — this applies both at approval and when editing post-approval. The note is stored permanently in the credit history and is visible to all admins. Always be specific: include the date, the approver, and the basis for the decision.
Producer (Supplier) — Additional Step at Approval
📋 Acknowledgement Required Flag

When approving a Producer, one additional option appears in the Review Application drawer:

FieldWhat it doesWhen to enable
Acknowledgement Required Synced to Odoo — the supplier must explicitly acknowledge each order before fulfilment can begin Enable for producers whose fulfilment process requires a manual confirmation step before dispatch. Check with the operations team if unsure
Quick Reference Checklists
👤 Customer Approval Checklist
  • Review all four sections on the application detail page (General, Contact, Address, Delivery)
  • Open the Review Application drawer → set Status to Approved
  • Assign a Customer Group (or leave blank for Standard pricing)
  • Decide whether to enable Credit — if yes, enter the limit and a reason note
  • Save — welcome email is sent automatically
  • Confirm the applicant is no longer in the Pending list
🏭 Producer Approval Checklist
  • Review all four sections — pay extra attention to Business Registration Number, Business Description, and Website
  • Cross-check Business Registration Number against Companies House if needed
  • Open the Review Application drawer → set Status to Approved
  • Set Acknowledgement Required flag if the operations team requires it for this producer
  • Save — welcome email is sent automatically
  • Confirm the applicant is no longer in the Pending list
Rejection Checklist — Both Account Types
  • Open the Review Application drawer → set Status to Rejected
  • Enter a clear Rejection Reason (minimum 10 characters) — this is stored internally for audit purposes
  • Save
  • Manually contact the applicant — no automated rejection email is sent. Use the contact email from the application
  • Rejected applications are visible in the Rejected view for future reference
🔧 Post-Approval Tasks (if needed later)

These actions can be performed at any time after approval from the Customer or Application detail page.

TaskWhereNotes
Change a customer's group Admin → Customers → [Customer] → Customer Groups section Add or remove groups; takes effect immediately on next login / price fetch
Enable credit after approval Admin → Applications → [Application] → Credit section (right sidebar) Click "Enable Credit", enter limit and reason note
Change credit limit Admin → Applications → [Application] → Credit section → Change Limit Reason note is mandatory whenever the limit amount changes
Disable credit Admin → Applications → [Application] → Credit section → Disable Credit Takes effect immediately; no note required
View credit history Admin → Applications → [Application] → Credit section → View History Full audit trail of all limit changes with dates, amounts, reasons, and who made the change
Resend welcome / setup email Direct the user to /forgot-password on the storefront See the Onboarding Process playbook for full details

🛒 Ecom-Backend

The central commerce engine that powers the entire Sesame platform — from buyer onboarding and product browsing through to checkout, payment, and delivery.

🌐 What is the Ecom-Backend?

The Ecom-Backend (simplifab-ecom-server) is the backbone of the Sesame platform. Every action a buyer or producer takes on the storefront — registering, browsing products, placing an order, requesting a delivery slot — is processed and stored here. It also drives the Admin panel, the Logistics portal, and the Data Exchange Service that syncs orders into Odoo.

Built on Medusa v2 (an open-source commerce framework), it has been extensively customised with 13 bespoke modules covering food-specific product data, B2B organisation management, delivery logistics, sourcing briefs, and more.

🗺️ Who It Serves
PortalWho uses itWhat it handles
Storefront (B2C/B2B)Buyers & ProducersProduct browsing, cart, checkout, orders, favourites, sourcing briefs, account & member management
Admin PanelInternal teamOrganisation approvals, pricing bands, partner management, product bulk uploads, seller overrides
Logistics PortalDelivery agentsOrder assignment, delivery slot management, delivery preferences, agent stats
AnalyticsInternal teamSales reporting and daily dashboards
📦 Custom Modules

13 custom modules extend the core commerce platform with Sesame-specific business logic.

ModuleWhat it does
OrganizationCore identity for buyers and sellers — company profiles, approval status, pricing bands, member management, and token-based onboarding
Product (custom)Extends standard products with food-specific data: nutrition, allergens, ingredients, dietary tags, certifications, and storage conditions
Order SetGroups multiple orders from a single checkout, tracking combined totals and fulfilment status across split orders
Split Order PaymentTracks how a payment is divided across individual orders when a buyer checks out a mixed cart
Logistics UserManages delivery agent accounts and their access to the Logistics portal
Logistics SlotDefines delivery time windows (AM/PM by day) with capacity limits, cutoff rules, and per-organisation delivery preferences
BriefsAllows buyers to submit sourcing briefs — category-level or product-level requests to find new suppliers or products
FavoritesLets buyers save products or producers to a personal favourites list
Buyer PreferenceStores a buyer's category preferences and commercial filters to personalise their experience
Supplier PreferenceStores a seller's service capabilities and preferences for matching and discovery
PartnerA curated directory of third-party service providers that Sesame recommends to its users
Lead RegistrationCaptures early-stage interest from potential buyers or sellers before full onboarding
Audit LogConfiguration-driven audit trail — records changes across key data tables for compliance and history

🚚 Logistics

An internal operations portal used by the Sesame logistics team to manage last-mile delivery of B2B food orders — from slot assignment through to delivery confirmation.

🌐 What is the Logistics Portal?

The Logistics portal (Logistics-V1) is an internal tool used exclusively by the Sesame logistics team — not by buyers or producers. Its job is to bridge the gap between an incoming customer order and a confirmed, scheduled delivery. New orders flow in automatically from the Ecom-Backend and appear in the portal waiting to be assigned a delivery slot or routed to a courier.

The dashboard refreshes automatically every 10–30 seconds so the team always sees live order data without needing to reload the page.

👥 Who Uses It
RoleWhat they do
Logistics AgentDay-to-day operations — assigns delivery slots, changes or cancels deliveries, sends notifications to purchasers
Logistics ManagerSame capabilities as an agent; cancellation and override actions are attributed to this role in the audit trail
AdminHighest permission level — override actions are logged against this role for auditability
🗺️ Main Sections
SectionWhat it showsStatus
Logistics Dashboard The operational hub — live tables of unassigned orders (needing a slot) and assigned orders (already scheduled). Shows how many deliveries are due in the next 7 days. Live
Purchaser Delivery Slot Allocation Manage standing delivery windows for regular buyers — view which recurring slots each purchaser has been allocated and add or edit them. Live
Warehouse Collections Manage stock collections from suppliers and warehouses — collection schedules, time windows, vehicle and driver assignment. Coming soon
⚙️ Core Workflows
WorkflowHow it works
Assign a delivery slot Unassigned orders appear in the dashboard. Click Assign to open a drawer showing the order items and purchaser details, then either pick an internal slot from the weekly calendar (day + AM/PM window) or route to a third-party courier with a tracking reference and cost estimate. Optional: notify the purchaser of the assignment.
Change or cancel a slot For already-assigned orders, click Change to pick a new slot or Cancel to return the order to the unassigned queue. Cancelling a slot does not cancel the order itself.
Bulk delivery notifications Select multiple assigned orders and send a delivery change notification to the relevant purchasers in one action.
Manage standing slots On the Purchaser Delivery Slot Allocation page, allocate or edit recurring weekly delivery windows for regular wholesale buyers (e.g. every Tuesday AM).
View order history A read-only log of all delivered orders, searchable by order number, purchaser name, address, or item. Expandable rows show the full item breakdown, delivery notes, and any return or refund information.

🧑‍💼 Admin

The internal control room for the Sesame platform — used by the operations and account management team to run the day-to-day of the marketplace.

🌐 What is the Admin Panel?

The Admin panel (administration) is an internal tool used by the Sesame team — ops staff, account managers, and platform administrators. It is not visible to buyers or producers. It covers everything from approving new business applications and managing the product catalogue, to processing orders, setting pricing, and configuring platform-wide rules. Order management is the default landing view, reflecting the primary daily workflow for the ops team.

🗺️ Main Sections
SectionWhat it covers
ApplicationsOnboarding queue for new buyers and producers — pending approvals, rejections, and accounts awaiting setup. See the Admin Review Checklist playbook for the full approval process.
OrdersFull order lifecycle — view, fulfil, ship, process returns, exchanges, refunds, and financial settlement
Customers & GroupsManage buyer accounts and segment them into customer groups, which determine which price list applies to each buyer
Products & InventoryCatalogue management — add and edit products, variants, stock levels, media, product types, and tags
Categories & CollectionsOrganise the product catalogue into browsable structures for the storefront
Price ListsCreate and manage custom pricing configurations tied to customer groups; add or edit per-product prices
Promotions & CampaignsConfigure discount rules and link them to marketing campaigns
Partners & ContactsManage supplier and partner organisations and their key contacts
TranslationsMaintain localised content for catalogue entities across supported languages, with completion tracking per locale
Platform SettingsRegions, tax rules, shipping options, sales channels, refund & return reasons, API keys, and user management
⚙️ Core Capabilities
  • Approve or reject buyer and producer applications
  • Assign customers to groups to control their pricing tier
  • Enable and manage credit accounts for buyers
  • Process the full order lifecycle including returns and exchanges
  • Add, edit, and organise the product catalogue
  • Create and manage price lists linked to customer groups
  • Set up promotions and discount campaigns
  • Configure regions, taxes, and shipping options
  • Manage platform users and API access
  • Maintain multi-language catalogue content

🏢 ERP — Odoo

Odoo 19 is the back-office ERP for the Sesame platform — handling procurement, fulfilment, stock, financials, and last-mile delivery tracking. Four custom modules connect it tightly to the Medusa storefront and Detrack delivery platform.

🌐 What is the ERP?

Odoo acts as the operational system of record for the Sesame marketplace. While buyers and producers interact with the storefront, the commercial and fulfilment reality lives in Odoo — stock levels, purchase orders, sale orders, supplier pricing, and financial settlement all flow through here. It is not directly visible to buyers or producers; they experience its effects through order confirmations, delivery updates, and product availability on the storefront.

Four custom modules form a single integration pipeline: products defined in Odoo flow out to the Medusa storefront; orders placed on Medusa flow back into Odoo as sale orders; and completed deliveries tracked via Detrack push status updates back to both Odoo and Medusa in real time.

🔄 How Odoo Fits Into the Platform
Products created / updated in Odoo
Synced to Medusa storefront automatically
Buyer places order on storefront
Order received in Odoo as Sale Order
Fulfilment triggered → Detrack delivery job created
Delivery status pushed back to Medusa in real time
📦 Custom Modules

Four custom modules extend Odoo 19 with Sesame-specific business logic.

Customer & Producer Master Data zt_medusa_customer_producer

Extends Odoo's standard contact record with the extra fields needed to represent Sesame buyers and producers — the foundation that all other modules depend on.

Data managed
Medusa ID linking the Odoo contact to the storefront record
Business profile: type, size, year founded, number of sites, job title
Consent flags: terms accepted, marketing consent by channel (email, SMS, WhatsApp, phone)
Location metadata: delivery slot assignment, primary location flags
Product Catalogue Sync zt_medusa_product

Makes Odoo the source of truth for the marketplace product catalogue. Every time a product is created or updated in Odoo, the module automatically pushes the full product payload to the Medusa storefront. Sync status is tracked per product (pending / success / failed) with automatic retries.

Data synced to storefront
Core catalogue: brand, description, GTIN/barcode, SKU, category hierarchy
Food compliance: ingredients, allergens, dietary tags, certifications, nutritional info, storage conditions, shelf life
Commercial terms: MOQ, bulk discount pricing bands, fulfilment model, lead time
Images, SEO fields, cuisine tags, occasions, product highlights
Order Intake from Storefront zt_medusa_order

Receives orders placed on the Sesame storefront into Odoo as Sale Orders, triggers the procurement and fulfilment workflow, and pushes order lifecycle events back to Medusa. Also exposes a supplier-facing API so producers can view and confirm their purchase orders.

DirectionWhat happens
Storefront → OdooCreates Sale Orders with customer, line items, delivery date, and Stripe payment reference. Auto-confirms orders after the daily sales cutoff time.
Odoo → StorefrontPushes cancellation events and fulfilment-closed events (including delivered vs ordered quantities and any short-shipment refund amounts) back to Medusa.
Supplier APIProducers can list their purchase orders, confirm a PO, and update the quantities they will deliver — all without logging into Odoo directly.
Last-Mile Delivery via Detrack zt_detrack

Manages the full lifecycle of a Detrack delivery job for every warehouse outbound shipment. When Detrack updates a delivery status (in progress, completed, failed), the module captures the proof-of-delivery data and pushes the fulfillment status back to the storefront in real time.

EventWhat happens
Order confirmed in OdooA Detrack delivery job is created with recipient address, order lines, and scheduled delivery date
Stock assigned (picking ready)Detrack job status updated to Dispatched
Delivery completedOdoo picking auto-validated; proof of delivery captured (driver name, signature, photos); storefront updated to Delivered
Delivery failedFlagged for ops attention; storefront updated to Failed
Backorder createdA new sequenced Detrack job is spawned for each additional delivery attempt

📋 Version History

Documentation changelog — release notes and what changed in each version.

v1.1.0 2026-06-16
v1.0.0 2026-01-15
  • Initial release — Network Topology diagram with Production and QA VPC layout.
  • System overview — droplet map, Nginx entry points, and service inventory.