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, logistics, docs, Medusa API, Keycloak) and the Odoo Droplet (Odoo web :8069 / longpoll :8072 upstream).
Within a droplet, services talk over localhost; across droplets they use the private VPC network only (Medusa ↔ DES, DES Worker → Odoo). Datastores are never publicly reachable — the DO Managed Postgres is VPC-only, and Redis / RabbitMQ / the Keycloak & Odoo Postgres bind to their host.
SSH is RSA-key only on :22; password authentication is disabled on all servers.
Buyers · Producers · Admin · Logistics · Ops users"] subgraph CF["☁️ Cloudflare (edge)"] WAF["WAF · DDoS · Bot filter
DNS proxy — hides origin IPs
TLS to origin: Full (strict)"] end 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 · logistics · docs · api · auth"] subgraph APPSVC[" "] direction LR SPA["Static SPAs (served from disk)
storefront · admin · logistics · docs"] MEDA["Medusa :9000 (PM2 cluster)"] KCA["Keycloak :8080 (Docker)"] end REDIS[("Redis :6379
event bus · cache · locks")] end subgraph DESD["🟣 DES Droplet — Ubuntu"] direction TB DESSA["DES Server :3000 (PM2)"] DESWA["DES Worker (PM2)"] RMQA["RabbitMQ :5672
mgmt :15672"] end subgraph ODOOD["🟢 Odoo Droplet — Ubuntu"] direction TB NGINXO["⬡ Nginx :443 (TLS)
public entry point"] ODOOA["Odoo 19 (Docker)
web :8069 · longpoll :8072"] ODOOPG[("Odoo PostgreSQL :5432
(Docker, local to droplet)")] end MPG[("DO Managed PostgreSQL :5432
Medusa DB · TLS, VPC-only")] KCPG[("Keycloak PostgreSQL :5432")] end %% ── Public ingress (network reachability only) ── INET -->|HTTPS 443| WAF WAF -->|"443 → App Droplet"| NGINXA WAF -->|"443 → Odoo Droplet"| NGINXO %% ── 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 edge;
| Service | URL |
|---|---|
| Storefront | qa.app.sesame.market |
| Admin UI | qa.admin.sesame.market |
| Logistics | qa.logistics.sesame.market |
| TCMS | qa.tcms.sesame.market |
| Odoo ERP | qa.opshub.sesame.market |
| Data Exchange | qa.dataexchange.sesame.market |
| Auth (Keycloak SSO) | qa.auth.sesame.market |
| Docs Hub | qa.docs.sesame.market |
| Service | URL |
|---|---|
| Storefront | uat.app.sesame.market |
| Admin UI | uat.admin.sesame.market |
| Logistics | uat.logistics.sesame.market |
| Odoo ERP | uat.opshub.sesame.market |
| Data Exchange | uat.dataexchange.sesame.market |
| Auth (Keycloak SSO) | uat.auth.sesame.market |
| Service | URL |
|---|---|
| Storefront | alpha.app.sesame.market |
| API | api.sesame.market |
| Admin UI | alpha.admin.sesame.market |
| Logistics | alpha.logistics.sesame.market |
| Odoo ERP | alpha.opshub.sesame.market |
| Data Exchange | alpha.dataexchange.sesame.market |
| Auth (Keycloak SSO) | alpha.auth.sesame.market |
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.
| Domain | Service | Type | Internal target | Location | Notes |
|---|---|---|---|---|---|
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 |
| Domain | Service | Type | Internal target | Location | Notes |
|---|---|---|---|---|---|
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 |
- 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, Logistics and Docs sign in via Keycloak (OIDC + PKCE, realm sesame) — Admin/Logistics then exchange the Keycloak token for a Medusa JWT — while Buyers/Producers use Medusa's native email/password auth.
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).
clients: medusa_admin · logistics · docs-hub
Auth Code + PKCE (S256)"] %% ═══════════ FRONTENDS ═══════════ subgraph FE["🖥️ Frontend SPAs (static, Nginx)"] direction LR SF["Storefront
simplifab-v2 · React"] ADM["Admin UI
administration · React"] LOG["Logistics UI
Logistics-V1 · React"] DOCS["Docs Hub
static · keycloak-js"] end %% ═══════════ CORE COMMERCE ═══════════ subgraph CORE["🛒 Core Commerce — App Droplet"] direction TB MED["Medusa v2.12.5 :9000 (PM2)
16 custom modules · 70 workflows
store · /admin · /logistics · /b2b APIs"] PG[("PostgreSQL
DO Managed")] REDIS[("Redis
event bus · workflows
cache · locks · sessions")] VOL["Local volume
images & assets"] end %% ═══════════ INTEGRATION (DES) ═══════════ subgraph DES["🔀 Data Exchange Service — DES Droplet (stateless)"] direction TB DESS["DES Server :3000 (PM2)
POST /api/webhook · x-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
zt_medusa_order · _product · _customer
zt_detrack · sesame_qc_hold · barcode"] ODOOPG[("Odoo PostgreSQL")] end %% ═══════════ EXTERNAL ═══════════ subgraph EXT["🌐 External Services"] direction LR STRIPE["💳 Stripe"] SENDGRID["✉️ SendGrid"] CFAI["🤖 Cloudflare AI"] DETRACK["📦 Detrack"] end %% ── Auth: SSO (dashed) vs native ── ADMINU -->|opens| ADM LOGU -->|opens| LOG DOCSU -->|opens| DOCS BUYER -->|opens| SF ADM -. "OIDC login-required" .-> KC LOG -. "OIDC login-required" .-> KC DOCS -. "OIDC PKCE (keycloak-js)" .-> KC ADM -->|"exchange /auth/keycloak → JWT"| MED LOG -->|"exchange /auth/keycloak/logistics"| MED SF -->|"emailpass (native) + REST"| MED %% ── Core data & storage ── MED --> PG MED --> REDIS MED --> VOL %% ── Medusa → DES → Odoo (orders, customers, producers) ── MED ==>|"webhook: ORDER/CUSTOMER/PRODUCER
HTTP + x-api-key"| DESS DESS ==>|publish| RMQ RMQ ==>|consume| DESW DESW ==>|"Odoo REST /json/2/{model}
Bearer token"| ODOO %% ── Odoo → DES → Medusa (products, fulfillment, marketing) ── ODOO -->|"webhook: PRODUCT/FULFILLMENT
CUSTOMER_MARKETING"| DESS DESW -->|"/admin/* · /dataexchange
Basic auth"| 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| CFAI %% ── 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; class KC sso; class PG,REDIS,ODOOPG,RMQ store; class MED core;
| Service | Stack | Version | Hosting | Deploy Trigger | Rollback |
|---|---|---|---|---|---|
| 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 |
| Setting | Value |
|---|---|
| Main queue | data-exchange-queue |
| Dead letter queue | data-exchange-queue-dlq |
| Max retries | 5 |
| Backoff | 5s, 10s, 20s, 40s, 80s (exponential) |
| Consumer timeout | 30 seconds |
| Max message size | 1 MB |
| 4xx errors | → DLQ immediately (no retry) |
| 5xx / timeout | → retry up to 5x → DLQ |
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.
-
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-Signatureheader. 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:150before go-live.
-
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.
-
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.
- 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;
.envfiles 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.
| Surface | Method | Actors | Status |
|---|---|---|---|
| Medusa Store API | JWT (emailpass actor) | Buyers, Producers | In place |
| Medusa Admin API | JWT (emailpass actor) | Admin users | In place |
| Medusa Logistics API | JWT (logistics actor) | Logistics agents/managers | In place |
| DES Webhook endpoint | API key (X-API-Key header) | Medusa backend only | In place |
| DES Admin endpoints | Bearer token | Internal admin | In place |
| Odoo API | Bearer token (Odoo API key) | DES Worker | In place |
| GitHub Actions → Servers | RSA SSH key (deploy key) | CI/CD only | In place |
| Production deploy approval | GitHub Environment protection | Senior dev | Not configured |
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.
Full branching strategy including hotfix flow is documented in the Developer Guide → Branching Strategy section.
rollback-production.yml — accepts specific release timestamp or defaults to previous.
develop if either tool reports a blocker.
| Aspect | Detail |
|---|---|
| What it catches | Code smells, duplications, complexity, SQL injection, XSS, hardcoded secrets, insecure patterns |
| Applies to | Medusa backend, DES, TCMS (TypeScript/Node.js repos) |
| Hosting option | SonarCloud (SaaS, free for public repos) or self-hosted SonarQube Community on a droplet |
| CI trigger | On every PR to develop — blocks merge if Quality Gate fails |
| Quality Gate | Coverage > 70%, 0 blocker issues, 0 critical security hotspots unreviewed |
| GitHub integration | PR decoration — inline comments on offending lines |
| Status | Not yet integrated |
- Create SonarCloud org linked to GitHub org (or deploy SonarQube Community to a DO Droplet)
- Add
sonar-project.propertiesto each repo root - Add
SONAR_TOKENto GitHub Actions secrets - Add a
sonar-scan.ymlworkflow step that runs before the build step on PRs todevelop - Configure branch analysis for
mainanddevelop - Set Quality Gate thresholds and enforce PR blocking via GitHub branch protection
npm dependencies and Docker images| Aspect | Detail |
|---|---|
| What it catches | Known CVEs in npm dependencies, outdated packages with exploits, Docker image vulnerabilities, license compliance issues |
| Applies to | All repos with package.json + Odoo Docker image |
| Hosting | Snyk SaaS — free tier covers open source scanning |
| CI trigger | On every PR + nightly scheduled scan on main |
| Block threshold | Critical and High severity CVEs with a fix available block the PR |
| Container scan | Scan odoo:19 Docker image for OS-level CVEs on each Odoo deploy |
| Status | Not yet integrated |
- Create Snyk account, link to GitHub org — Snyk auto-imports all repos
- Add
SNYK_TOKENto GitHub Actions secrets - Add
snyk teststep to each repo's PR workflow (runs afternpm ci) - Add
snyk container test odoo:19to the Odoo deploy workflow - Configure Snyk PR checks in GitHub to block merge on critical CVEs
- Enable Snyk nightly monitor on
mainfor new vulnerability disclosures
| PM2 Process | Droplet | Script | Working Dir | Port | Restarts |
|---|---|---|---|---|---|
medusa-backend | App Droplet | npm run start | /home/deploy/backend/current/.medusa/server | 9000 | On crash |
data-exchange-server | DES Droplet | dist/server.js | /home/deploy/des/current | 3000 | On crash |
data-exchange-worker | DES Droplet | dist/worker.js | /home/deploy/des/current | — | On crash |
- 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.
| Asset | Env | Method | Retention |
|---|---|---|---|
| PostgreSQL (Medusa) | Prod | DO Managed — automatic | 7 days |
| PostgreSQL (Odoo) | Prod | TODO | TODO |
| Mounted volume (assets) | Prod | TODO | TODO |
| RTO (recovery time) | Prod | TODO | |
| RPO (data loss tolerance) | Prod | TODO | |
Medusa backend + DES
Run:
npm test
Medusa backend
Real DB required
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.
| Module | Risk |
|---|---|
logistics-user | Medium |
logistics-slot | Medium |
organization | High |
order-set | High |
recurring-order | High — scheduler + workflow |
split-order-payment | High — payment logic |
audit-log | High — DB triggers, must load last |
product (custom) | Medium |
briefs | Low |
favorites | Low |
supplier_preference | Low |
buyer_preference | Low |
- 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 at2.12.5) - Run
medusa db:migrateon 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-sdkwhich 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
| Module | Dependencies | Risk |
|---|---|---|
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 |
- 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
| Package | Current | Upgrade Effort | Notes |
|---|---|---|---|
| Node.js | ≥ 20 | Low | Follow Medusa's Node.js requirement. Test PM2 compatibility. |
| MikroORM | 6.4.16 | Medium | Bundled with Medusa. Only upgrade as part of Medusa upgrade, not independently. |
| Stripe SDK | 3.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. |
| amqplib | 0.10.4 | Low | Stable library. Test DES queue consumer/publisher after any upgrade. |
| PostgreSQL | 15 (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.
| 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.146157.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.182129.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 |
0c3c8352-9f6f-483e-9745-aad7d9c65b95 ·
Mounted volume (prod-medusa): 6ea6b602-bd5b-11f0-9ecf-0a58ac12d651 ·
Total: $84 / mo
| 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.199188.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.155157.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.51129.212.202.28 |
10.106.0.4 |
$24 / mo | active | 2025-12-31 |
d44d5b2d-9194-466d-b637-e4582e6fd219 ·
Total: $72 / mo
- 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
- Backups — not enabled on qa-odoo, qa-data-exchange-service
- IPv6 — enabled on prod-data-exchange-service; optional on others
| Environment | Droplets | $/mo |
|---|---|---|
| Production | 3 droplets | $84 |
| QA | 3 droplets | $72 |
| DO Managed PostgreSQL | — | not tracked here |
| Droplets total | $156 / mo | |
- qa-medusa is running Ubuntu 25.04 — this image is marked
retiredby 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.
| 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 |
Host:
sesame-prod-pg-db-do-user-26720238-0.j.db.ondigitalocean.comPrivate host:
private-sesame-prod-pg-db-do-user-26720238-0.j.db.ondigitalocean.comPort:
25060 · SSL required · Users: doadmin (primary)
Host:
db-postgresql-odoo-do-user-26720238-0.d.db.ondigitalocean.comPrivate host:
private-db-postgresql-odoo-do-user-26720238-0.d.db.ondigitalocean.comPort:
25060 · SSL required · Users: doadmin (primary), app_user
| ID | Attached to | Environment | Purpose |
|---|---|---|---|
6ea6b602-bd5b-11f0-9ecf-0a58ac12d651 |
prod-medusa | Production | Application data, uploaded images & assets |
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.
- Cut
feature/xxxfromdevelop - Build and commit on the feature branch
- Raise a PR to merge into
develop - Peer review required — no self-merge
- On merge → QA deploy triggers automatically
- When QA is signed off → trigger prod deploy via
workflow_dispatch - Successful prod deploy auto-merges
develop → mainand creates a release tag
- Cut
hotfix/xxxdirectly frommain - Apply the fix and commit
- Merge
hotfix/xxx → main(triggers prod deploy) - Sync the fix back: merge
hotfix/xxx → develop - Delete the hotfix branch after both merges
main, not develop, so production gets only the minimal fix without picking up unreleased QA work.
main— production-ready only, protecteddevelop— protected, peer review requiredfeature/xxx— branch fromdevelophotfix/xxx— branch frommain- No direct commits to
mainordevelop - Commit messages must reference issue number
- No
qabranch — QA environment =develop
- Odoo — branching not yet streamlined; deploy still runs directly from
mainvia 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.
| Environment | LOG_LEVEL | Format | Output |
|---|---|---|---|
| Development | debug | Human-readable, no timestamp | Console only |
| Production | info | JSON + timestamp | File (LOG_FILE env var) |
- 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
Always pass metadata as an object (second param). Never concatenate strings.
logger.info("Order created", { orderId, total })
debug for dev detail · info for normal ops · warn for attention needed · error for failures
Declare
const LOG_PREFIX = "ClassName" at the top of every file. Prefix every log message.logger.info(`${LOG_PREFIX}: Request received`, { orderId })
See list above. Use
sanitizeEmail() helper for email addresses.
Before processing starts, log essential request info on every API endpoint.
Every call to Stripe, SendGrid, Detrack, S3, Cloudflare AI, Odoo — log it with duration.
After processing completes, log the outcome with relevant context.
Every
catch block must log error.message + error.stack. Re-throw after logging.
Never log entire objects. Pick specific fields. No
console.log — use logger only.
No
TEMP:, TODO:, or console.log commits. Permanent logs only.
Emojis break log aggregation tools. Plain text only.
✗ "✅ Order created" ✓ "Order created"
Log successful logins, failed logins (with
sanitizeEmail), and all unauthorised access attempts.
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.
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.
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
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
| Area | Who uses it | What it covers |
|---|---|---|
| Public / Marketing | Anyone | Home page, how it works, contact, terms, public producer profiles — no login required |
| Registration & Auth | New applicants | Buyer and producer registration, email verification, password setup and reset, team invitations |
| Buyer Portal | Buyers | Dashboard, product search & browse, product detail, cart, checkout, order tracking, favourites, sourcing briefs, account settings |
| Producer Portal | Producers | Order / purchase order management, product management, analytics, settings |
| Internal / Ops | Internal team | Logistics 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.
The customer navigates to /register/buyer on the Sesame storefront. They complete the following fields:
| Field | Required | Notes |
|---|---|---|
| First Name | Yes | Letters, hyphens and apostrophes only |
| Last Name | Yes | Letters, hyphens and apostrophes only |
| Email Address | Yes | Validated for uniqueness in real time — duplicate emails are rejected immediately |
| Phone Number | Yes | Country code + number (supports +44, +1, +33, +49). Validated for uniqueness in real time |
| Role / Designation | Yes | Free text, e.g. "Head Chef", "Procurement Manager" |
| Company Name | Yes | Trading name of the buyer's business |
| Field | Required | Notes |
|---|---|---|
| Address Line 1 | No | Helps estimate delivery options |
| Address Line 2 | No | |
| County | No | UK searchable select |
| City | No | |
| Postcode | No | Auto-uppercased |
| Field | Required |
|---|---|
| How did you hear about Sesame? | No |
| Agree to Terms & Conditions | Yes |
| Marketing Consent | No |
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.
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.
Once the admin approves the application, the platform automatically sends the customer a welcome email containing a unique account setup link.
| Detail | Value |
|---|---|
| Link destination | /setup-account?token=<unique_token> |
| What the link does | Takes the customer to a page where they set their password for the first time |
| Token expiry | Tokens 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 page | Organisation name and email address are pre-filled and read-only; only password and confirm password are entered |
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
/loginusing their email and the password they just set.
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.
| Field | Required | Notes |
|---|---|---|
| First Name | Yes | Letters, hyphens and apostrophes only |
| Last Name | Yes | Letters, hyphens and apostrophes only |
| Email Address | Yes | Personal email; validated for uniqueness in real time |
| Phone Number | Yes | Country code + number; validated for uniqueness |
| Role / Designation | Yes | e.g. "CEO", "Managing Director", "Head Chef" |
| Field | Required | Notes |
|---|---|---|
| Legal Entity / Business Name | Yes | Validated for uniqueness in real time |
| Business Email | No | If different from personal email; must not match personal email |
| Business Registration Number | Yes | 8 alphanumeric characters; validated for uniqueness |
| Year Founded | Yes | 4-digit year between 1800 and current year |
| Size of Business | Yes | 1–5 / 6–20 / 21–50 / 51+ employees |
| Business Type | Yes | Restaurant, Cafe, Bar, Retailer, Caterer, Wholesaler, Manufacturer, or Other |
| Business Description | Yes | 20–500 characters |
| Website / Social Media | No | Can tick "My business does not have a website" |
| Business Address | No | Line 1, Line 2, County, City, Postcode |
| Field | Required |
|---|---|
| How did you hear about Sesame? | No |
| Agree to Terms & Conditions | Yes |
| Marketing Consent | No |
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.
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.
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.
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.
| Aspect | Customer (Buyer) | Producer (Supplier) |
|---|---|---|
| Registration URL | /register/buyer | /register/supplier |
| Form complexity | Shorter — personal info + company name + optional address | Longer — full business details including registration number, year founded, size, type, description |
| Business registration number | Not collected | Required — 8 alphanumeric characters, must be unique |
| Separate business email | Not collected | Optional field (must differ from personal email) |
| Post-approval dashboard | Buyer Dashboard (/buyer/dashboard) | Supplier Dashboard (/supplier/dashboard) |
| Welcome email trigger | Same — sent on admin approval | Same — sent on admin approval |
| Account setup link URL | /setup-account?token=… — same | /setup-account?token=… — same |
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.
| Scenario | What happens |
|---|---|
Valid token, user visits /reset-password?token=… | Sees the new-password form with their email pre-shown (read-only) |
| Expired or invalid token | Sees an error card with a "Request New Reset Link" button — clicking it returns them to /forgot-password |
| User successfully sets new password | Sees a "Password Reset Complete" confirmation and a Sign In button |
A logged-in user can change their password from their account settings. The path depends on account type:
| Account type | Settings path |
|---|---|
| Customer (Buyer) | /buyer/settings/password |
| Producer (Supplier) | /supplier/settings/password |
| User reports | Likely cause | Action |
|---|---|---|
| "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.
All incoming applications are found under the Applications section in the Admin panel. There are three views:
| View | What it shows | When 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 |
The application detail page shows four sections. Review all of them before making a decision.
| Section | Key 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 |
- 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)
Click Edit on the application detail page to open the Review Application drawer. The first decision is whether to approve or reject.
- 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
- 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
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.
| Option | What 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 |
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.
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.
- 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
- 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
When approving a Producer, one additional option appears in the Review Application drawer:
| Field | What it does | When 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 |
- 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
- 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
- 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
These actions can be performed at any time after approval from the Customer or Application detail page.
| Task | Where | Notes |
|---|---|---|
| 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.
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.
| Portal | Who uses it | What it handles |
|---|---|---|
| Storefront (B2C/B2B) | Buyers & Producers | Product browsing, cart, checkout, orders, favourites, sourcing briefs, account & member management |
| Admin Panel | Internal team | Organisation approvals, pricing bands, partner management, product bulk uploads, seller overrides |
| Logistics Portal | Delivery agents | Order assignment, delivery slot management, delivery preferences, agent stats |
| Analytics | Internal team | Sales reporting and daily dashboards |
13 custom modules extend the core commerce platform with Sesame-specific business logic.
| Module | What it does |
|---|---|
| Organization | Core 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 Set | Groups multiple orders from a single checkout, tracking combined totals and fulfilment status across split orders |
| Split Order Payment | Tracks how a payment is divided across individual orders when a buyer checks out a mixed cart |
| Logistics User | Manages delivery agent accounts and their access to the Logistics portal |
| Logistics Slot | Defines delivery time windows (AM/PM by day) with capacity limits, cutoff rules, and per-organisation delivery preferences |
| Briefs | Allows buyers to submit sourcing briefs — category-level or product-level requests to find new suppliers or products |
| Favorites | Lets buyers save products or producers to a personal favourites list |
| Buyer Preference | Stores a buyer's category preferences and commercial filters to personalise their experience |
| Supplier Preference | Stores a seller's service capabilities and preferences for matching and discovery |
| Partner | A curated directory of third-party service providers that Sesame recommends to its users |
| Lead Registration | Captures early-stage interest from potential buyers or sellers before full onboarding |
| Audit Log | Configuration-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.
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.
| Role | What they do |
|---|---|
| Logistics Agent | Day-to-day operations — assigns delivery slots, changes or cancels deliveries, sends notifications to purchasers |
| Logistics Manager | Same capabilities as an agent; cancellation and override actions are attributed to this role in the audit trail |
| Admin | Highest permission level — override actions are logged against this role for auditability |
| Section | What it shows | Status |
|---|---|---|
| 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 |
| Workflow | How 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.
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.
| Section | What it covers |
|---|---|
| Applications | Onboarding queue for new buyers and producers — pending approvals, rejections, and accounts awaiting setup. See the Admin Review Checklist playbook for the full approval process. |
| Orders | Full order lifecycle — view, fulfil, ship, process returns, exchanges, refunds, and financial settlement |
| Customers & Groups | Manage buyer accounts and segment them into customer groups, which determine which price list applies to each buyer |
| Products & Inventory | Catalogue management — add and edit products, variants, stock levels, media, product types, and tags |
| Categories & Collections | Organise the product catalogue into browsable structures for the storefront |
| Price Lists | Create and manage custom pricing configurations tied to customer groups; add or edit per-product prices |
| Promotions & Campaigns | Configure discount rules and link them to marketing campaigns |
| Partners & Contacts | Manage supplier and partner organisations and their key contacts |
| Translations | Maintain localised content for catalogue entities across supported languages, with completion tracking per locale |
| Platform Settings | Regions, tax rules, shipping options, sales channels, refund & return reasons, API keys, and user management |
- 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.
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.
Four custom modules extend Odoo 19 with Sesame-specific business logic.
zt_medusa_customer_producerExtends 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 |
zt_medusa_productMakes 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 |
zt_medusa_orderReceives 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.
| Direction | What happens |
|---|---|
| Storefront → Odoo | Creates Sale Orders with customer, line items, delivery date, and Stripe payment reference. Auto-confirms orders after the daily sales cutoff time. |
| Odoo → Storefront | Pushes cancellation events and fulfilment-closed events (including delivered vs ordered quantities and any short-shipment refund amounts) back to Medusa. |
| Supplier API | Producers can list their purchase orders, confirm a PO, and update the quantities they will deliver — all without logging into Odoo directly. |
zt_detrackManages 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.
| Event | What happens |
|---|---|
| Order confirmed in Odoo | A 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 completed | Odoo picking auto-validated; proof of delivery captured (driver name, signature, photos); storefront updated to Delivered |
| Delivery failed | Flagged for ops attention; storefront updated to Failed |
| Backorder created | A new sequenced Detrack job is spawned for each additional delivery attempt |
Version History
Documentation changelog — release notes and what changed in each version.
- Initial release — Network Topology diagram with Production and QA VPC layout.
- System overview — droplet map, Nginx entry points, and service inventory.