Architecture Overview

Modular microservice architecture for the YourEra telemedicine platform. Each service is independently deployable, testable, and documented.

System Diagram

Eight services with clear boundaries. Arrows show HMAC-authenticated service-to-service calls.

React Intake SPAs (3 Vite apps) Admin Portal (React SPA) Shipping Dashboard (React SPA) Patient Portal (Express BFF) Intake Gateway NEW — Express 5 + TS Integration Service SLIMMED — Admin + Canvas Prescription Orchestrator NEW — Pipeline Coordinator Physician Registry EXISTS — Enhanced Pharmacy Router EXISTS Notification Service NEW — Configurable Shipping Service NEW — + Print Queue Canvas FHIR Stripe PioneerRx Boothwyn Strive FedEx SES / Twilio Exists New Slimmed

Pipeline Walkthrough

The complete patient journey from intake to shipment, showing which service handles each step.

1
Patient Completes Intake

Patient fills out medical history, selects plan, pays via Stripe. React SPA encrypts data locally (AES-256-GCM) and submits to Intake Gateway.

2
Intake Validation & Canvas Patient

Intake Gateway validates the HMAC-signed plan token (amount matches Stripe), creates a FHIR Patient in Canvas, and queues an "Rx Review" task for physicians.

3
Physician Assignment

Physician Registry assigns the task to all eligible physicians for the patient's state. Multiple doctors see it in their RxQueue. First to claim wins.

4
Physician Reviews & Approves

Physician opens the case in Admin Portal, reviews H&P, selects dosage, and clicks Approve. The Integration Service forwards the approval to the Orchestrator.

5
Orchestrator Runs the Pipeline

Prescription Orchestrator executes 7 steps in sequence: resolve prescriber via Physician Registry, route pharmacy via Pharmacy Router, submit Rx, charge Stripe payment, notify patient via Notification Service, create shipment via Shipping Service, schedule refills.

6
Pharmacy Fills & Ships

For GMP (Galleria): Shipping Service auto-generates a FedEx label and adds the Rx to the print queue. For Boothwyn: the pharmacy ships independently and sends a webhook to the Pharmacy Router, which forwards the tracking number to the Integration Service via an HMAC-signed callback.

7
Patient Notified

Notification Service sends email + SMS with tracking info. Channels are configurable per notification type via the Admin Portal.

Service Catalog

Intake Gateway
Unified intake API. Validates data, handles Stripe payments with cryptographic plan tokens, creates Canvas patients, hands off to orchestrator.
New Express 5
Prescription Orchestrator
Pipeline coordinator. Resolves physician, routes pharmacy, submits Rx, charges payment, triggers notifications, creates shipments, schedules refills.
New Express 5
Physician Registry
Physician CRUD, license management, priority-based state routing, multi-physician claim model for pay-per-visit.
Exists Enhanced
Pharmacy Router
Routes prescriptions to PioneerRx, Boothwyn, or Strive. DB-driven state routing with adapters per pharmacy.
Exists
Notification Service
Sends email, SMS, HubSpot, and Slack notifications. DB-stored templates editable via Admin Portal. Channel toggles per notification type.
New Express 5
Shipping Service
FedEx labels, shipment lifecycle, tracking, Boothwyn webhooks, and Galleria print queue for pharmacy staff.
New Express 5
Integration Service
Slimmed monolith. Keeps admin API, Canvas webhook handlers, RxQueue endpoints, HubSpot sync, and patient portal routes.
Slimmed Next.js
GuideGLP
B2B physician ordering portal. Own auth, own orders, calls Pharmacy Router. Future: migrate to Physician Registry.
Exists
What is NOT a separate service Payment (105 lines of Stripe, only orchestrator uses it) and Canvas FHIR (each service includes the client library directly) stay as libraries. A separate auth service is unnecessary — each service manages its own auth.

Inter-Service Communication

All service-to-service calls use HMAC-SHA256 authentication (same pattern as Pharmacy Router and Physician Registry).

// Request headers
X-API-Key:    <identifies the caller>
X-Timestamp:  <ISO 8601, within 5 min>
X-Signature:  HMAC-SHA256("<timestamp>.<jsonBody>", apiSecret)

Call Graph

Intake Gateway Orchestrator submit after intake
Integration Svc Orchestrator task-action approve/deny
Orchestrator Physician Registry resolve prescriber for state
Orchestrator Pharmacy Router submit prescription
Orchestrator Notification Service notify patient
Orchestrator Shipping Service create FedEx shipment
Shipping Service Notification Service shipment notification
GuideGLP Pharmacy Router submit B2B prescription
GuideGLP Physician Registry verify license (future)

Database Topology

Each service owns its own database in the shared RDS instance. No cross-service DB access — services communicate only via APIs.

ServiceDatabaseKey Tables
Intake Gatewayintake_gatewayintake_plans, intake_submissions, intake_retry_queue, intake_verification_codes
Orchestratororchestratororchestration_runs, payment_holds, refill_schedules, patient_mappings, sync_logs, medication_configs
Physician Registryphysician_registryphysicians, physician_licenses, physician_routing, prescription_assignments, registry_api_keys
Pharmacy Routerpharmacy_routerrouter_submissions, router_pharmacy_config, router_api_keys
Notification Svcnotification_servicenotification_templates, notification_channel_config, notification_log
Shipping Serviceshipping_serviceshipments, shipping_users, tracking_cache, print_queue
Integration Svchisera (existing)admin_otp_codes, telehealth_appointments (shrinks over time)
GuideGLPguideglpguide_physicians, guide_orders, guide_refill_schedules, guide_invoice_periods

Multi-Physician Claim Model

Enables a pay-per-visit model where multiple physicians see the same patient. First to claim gets the case.

1
Assign

New intake arrives. Physician Registry finds all eligible physicians for the patient's state and creates a prescription_assignments row with status=open. Each eligible physician gets an entry in the junction table.

2
Notify

Registry calls Notification Service to alert each physician (email + Slack). Physicians see the task in their RxQueue.

3
Claim

Physician clicks "Review". Admin Portal calls POST /route/claim. Registry executes atomic SQL: UPDATE…WHERE status='open'. If already claimed by another physician, returns 409 Conflict. Race-safe via PostgreSQL row-level locking.

4
Complete

Physician approves/denies. Assignment status transitions to completed. The physician is credited for the visit.

-- Atomic claim (exactly one winner in a race)
UPDATE prescription_assignments
SET status = 'claimed',
    claimed_by = $physicianId,
    claimed_at = NOW()
WHERE id = $assignmentId
  AND status = 'open';

Notification System

All notifications flow through the Notification Service. Templates and channel toggles are stored in the database and editable via the Admin Portal.

Channel Matrix

Notification TypeEmailSMSHubSpotSlack
prescription_approvedONONON
prescription_deniedONON
shipment_updateONONON
payment_chargedONONON
payment_failedONON
provider_messageONONON
refill_reminderONON
welcomeONON
physician_new_taskONON
admin_alertON

Each toggle is stored in notification_channel_config and editable via PUT /notify/channels/:type. The Admin Portal provides a UI with toggle switches.

Part of the Shipping Service. When the Orchestrator creates a GMP shipment, a print queue entry is auto-created. Galleria pharmacy staff see it in the Shipping Dashboard.

-- Print queue lifecycle
pending    printing    printed    (flows into pack/ship)
                         error      (retry)
EndpointMethodPurpose
/shipping/print-queueGETList prescriptions pending print
/shipping/print-queue/:id/printedPOSTMark a prescription as printed

Secure Intake Validation

Prevents client-side price tampering. The Intake Gateway signs plan selections with HMAC, then verifies the signature matches the Stripe amount at payment time.

// 1. Server generates plan token when client selects a plan
const payload = { planId, amountCents, intakeType, ts: Date.now() };
const data = base64url(JSON.stringify(payload));
const sig  = HMAC-SHA256(data, PLAN_SIGNING_SECRET);
const planToken = data + '.' + sig;

// 2. Client sends planToken with Stripe PaymentIntent

// 3. Server verifies on payment submission
const [data, sig] = planToken.split('.');
const expected = HMAC-SHA256(data, PLAN_SIGNING_SECRET);
if (sig !== expected) throw 'Tampered plan token';
if (payload.amountCents !== stripeAmount) throw 'Amount mismatch';
if (Date.now() - payload.ts > 15 * 60 * 1000) throw 'Token expired';

Migration Phases

Services are extracted in dependency order — leaf services first, then services that depend on them.

1
Notification Service
Extract from lib/notifications/. Deploy, seed templates from hardcoded HTML. Update monolith to call the service. Add Admin Portal "Notifications" page.
Dependencies: None (leaf service)
2
Shipping Service
Extract from lib/fedex/ and shipment routes. Migrate shipments, shipping_users, tracking_cache tables. Add Galleria print queue. Update Shipping Dashboard.
Dependencies: Notification Service (Phase 1)
3
Prescription Orchestrator
Extract orchestrator.ts and supporting modules. Migrate payment_holds, refill_schedules, patient_mappings, sync_logs. Move refill cron here.
Dependencies: Physician Registry, Pharmacy Router, Notification Svc (Phase 1), Shipping Svc (Phase 2)
4
Intake Gateway
Rewrite JS intake server in TypeScript. Add cryptographic plan validation. DB-backed retry queue and pricing. Update React SPAs to point to new service.
Dependencies: Orchestrator (Phase 3)
5
Physician Registry Enhancement
Add multi-physician claim model. Seed YourEra prescribers. Update Orchestrator to use Registry for prescriber resolution. Remove hardcoded constants.
Dependencies: Orchestrator (Phase 3)
6
GuideGLP Integration
Migrate from own physician tables to Physician Registry API. Optionally route through Orchestrator for unified tracking.
Dependencies: Physician Registry (Phase 5)

ALB Routing

All services share a single domain (api.yourera.com) with path-based routing on the ALB.

PriorityPath PatternService
1/rx/*Pharmacy Router
2/physicians/*, /route/*Physician Registry
3/orchestrator/*Prescription Orchestrator
4/notify/*Notification Service
5/shipping/*Shipping Service
6/intake/*Intake Gateway
50/* (default)Integration Service

Testing Strategy

Each service follows the established pattern (vitest, same as Pharmacy Router):

Verification after each phase All existing tests still pass (no regressions), new service passes independently, end-to-end pipeline works, docs page renders on Vercel, health check returns 200 on the new ALB path.