Skip to content

CEO Plan: The Inbox Is The Product β€” Unified Order Processing UX

CEO Plan: The Inbox Is The Product β€” Unified Order Processing UX

Section titled β€œCEO Plan: The Inbox Is The Product β€” Unified Order Processing UX”

Generated by /plan-ceo-review on 2026-03-21 Branch: docs/unified-inbox-design-v2 | Mode: SCOPE EXPANSION Repo: ERP-Unlocked/ordermatic

β€œThe Intelligent Order Desk” β€” The unified inbox doesn’t just show orders, it works them:

  • Smart customer-to-rep routing based on ERP data (DEFERRED)
  • SLA timers visible on every row with configurable thresholds (ACCEPTED)
  • Inline order editing β€” full-width detail view with PDF + extracted data side-by-side, no page transitions (ACCEPTED)
  • Team performance dashboard β€” orders/person, time-to-ERP, auto-validation rate, coverage chart (ACCEPTED)
  • Snooze for orders waiting on customer confirmation (ACCEPTED)
  • β€œSend test order” for onboarding aha moment (ACCEPTED)

The user opens ordermatic and sees their work queue. Every order β€” email, upload, API β€” is a single row. The row tells them everything: who sent it, what’s in it, what confidence the AI has, whether it needs attention or was auto-submitted. They press Enter and see the PDF and extracted data side by side. They fix one item, press Cmd+Enter, and it’s in the ERP. They press j to move to the next order. 50 orders in 20 minutes.

The manager sees the same view but with team context: who’s working on what, what’s stuck, what’s about to breach SLA. They see a coverage chart: β€œThis week: 92% of orders through ordermatic.”

The experience feels like Superhuman for order processing β€” fast, keyboard-driven, zero-distraction.

Approach A: workItems table β€” New table that extracts inbox workflow state from emailEvents.providerMetadata and becomes the single source of truth for all order workflow. Separate FK columns (emailEventId, pdfDocumentId, orderId) for DB-enforced referential integrity.

Exactly one of emailEventId, pdfDocumentId, or clonedFromWorkItemId must be non-null (the source). orderId is nullable and set once extraction succeeds and an ERP order record is created. extractedOrderId is nullable and set once PDF extraction produces an extracted order. DB check constraint enforces exactly-one-source invariant: CHECK (num_nonnulls(email_event_id, pdf_document_id, cloned_from_work_item_id) = 1)

Uses existing hasElevatedInboxAccess() from src/lib/inbox/permissions.ts. Supervisors are users with roles: manager, org:manager, branch_manager, org:branch_manager, order_desk, org:order_desk, admin, org:admin, or org:owner. Regular users can only self-assign and manage their own work items.

The system uses an event-driven architecture β€” no polling:

Email β†’ Cloudflare Email Routing β†’ email-worker (CF Worker)
↓
[Parse MIME | Hard/soft filter | Alias lookup | LLM triage | Upload PDFs]
↓
POST /api/webhook/inbound-email (HMAC-signed)
↓
[Create emailEvent | Link PDFs | Start Temporal PDFExtractionWorkflow]
↓
Temporal: PDFExtractionWorkflow β†’ extract β†’ match β†’ enrich β†’ validate
↓ (on user submit or auto-validation pass)
Temporal: ERPOrderSubmissionWorkflow β†’ submit to ERP β†’ confirm

Where dual-write hooks in: The /api/webhook/inbound-email handler in the webapp creates the emailEvent. The workItem INSERT goes in the same transaction, right after the emailEvent INSERT. For uploads, the /api/pdf-documents upload handler creates the workItem.

Background jobs use Temporal, not the legacy Trigger.dev stack. New scheduled work (unsnooze cron, backfill) should be Temporal scheduled workflows. Dagster handles ERP data syncing only (customers, products, inventory) β€” not inbox workflow.

  1. Source FKs: Separate nullable FK columns (not polymorphic sourceId). See FK constraints above.
  2. SLA config: Org-level only (single threshold per org, extend to per-customer later)
  3. Feature flags: Single unified_inbox_v2 flag per org (not granular per-feature)
  4. Uploads: Auto-assigned to uploader, personal inbox. Only supervisors can reassign. Upload + workItem creation wrapped in a single DB transaction (no orphaned PDFs).
  5. Auto-archive: On ERP approval/completion. Clone from archive for reorders.
  6. Snooze: snoozedUntil timestamp on workItems, hidden from active view until then. SLA timer keeps ticking while snoozed β€” snooze hides the item from the active view but does not pause SLA obligations. Unsnooze cron runs every 5 minutes via a Temporal scheduled workflow. If the schedule misses a run, items stay hidden until next successful run (self-healing, no data loss). Temporal’s built-in schedule monitoring provides alerting on missed runs β€” configure a Temporal search attribute alert if unsnooze hasn’t run in >15 minutes. When a snoozed item resurfaces with elapsed SLA, the detail view shows a β€œSnoozed X hours β€” SLA continued during snooze” context badge so the user understands why the timer is high.
  7. Non-order emails: Emails triaged as intent=skipped do NOT create workItems. Inbox stays clean.
  8. Failed extractions: Stay in active inbox with error state + retry button (zero silent failures).
  9. Unsaved changes: Confirmation dialog when navigating away from edited item in split pane.
  10. Denormalized display fields: workItems stores title, senderName, senderEmail, receivedAt, sourceType, confidenceScore β€” set once at creation, never updated. Inbox list is a single-table query with no JOINs.
  11. Denormalized org/assignment: workItems stores clerkOrganizationId, assignedToUserId as proper indexed columns. Tab filtering (org/user/branch/all) works without joining emailAliases.
  12. Composite index: Primary index on (clerkOrganizationId, deletedAt, archivedAt, status, receivedAt DESC). Individual indexes on assignedToUserId, snoozedUntil, and source FKs.
  13. New bulk-actions endpoint: /api/work-items/bulk-actions targets workItems directly. Old /api/emails/bulk-actions stays for rollback during dual-write period, removed in Phase 8.
  14. Optimistic locking: workItems has a version integer column (default 1), incremented on every update. All mutations include WHERE version = :expectedVersion β€” if the row was modified concurrently, the update affects 0 rows and the API returns 409 Conflict. The frontend re-fetches and shows β€œThis item was updated by someone else” with a refresh button. This prevents data loss when two users edit the same extracted order or when a supervisor reassigns an item another user is actively reviewing.
  15. Test order flag: workItems created via β€œSend test order” onboarding have isTestOrder: true. Test orders are excluded from dashboard stats (coverage, SLA, team performance) but appear in the user’s inbox so they experience the full flow. Test orders are visually distinguished with a β€œTest” badge on the row.

Cloning an archived work item creates a new workItem with sourceType='clone' and a clonedFromWorkItemId reference. It copies the extractedOrder data (customer, items, shipping) into a new extractedOrder record. The original archived item is unchanged. The new item lands in the user’s personal inbox as status: new. Use case: repeat orders, seasonal reorders, corrections to previously submitted orders.

#ProposalEffortDecisionReasoning
1Smart customer-to-rep routingMDEFERREDGreat feature, but not critical for V1 unification
2Inline order editing (full-width detail)LACCEPTEDCore to the β€œSuperhuman for orders” vision
3SLA timers on inbox rowsMACCEPTEDDirectly drives urgency and order processing speed
4Team performance dashboardMACCEPTEDManagers are the buyers β€” they need visibility to champion adoption
5Snooze featureSACCEPTEDSmall effort, high value for β€œwaiting on customer” workflow
6Delight bundle (D1-D5, see below)SACCEPTEDPolish that makes the product feel intentional
7”Send test order” onboardingSACCEPTEDDramatically improves first-run aha moment
  • Inline order editing (full-width detail view: PDF left + form right, list hides)
  • SLA timers on inbox rows (org-level threshold)
  • Team performance dashboard (manager tab) β€” shows per-rep performance against org-level SLA
  • Snooze feature (snoozedUntil timestamp, SLA keeps ticking)
  • Delight bundle:
    • D1: Unread count in browser tab title
    • D2: ? key keyboard shortcut cheatsheet modal
    • D3: Source icons (email/upload/manual/clone) on each row
    • D4: β€œCopy email address” branded toast
    • D5: Empty state with visual pipeline progress
  • β€œSend test order” button in onboarding
  • Smart customer-to-rep routing based on ERP customer data
  • Primary: Weekly order coverage increases from 60-65% to 80%+ within 8 weeks of full rollout (coverage = orders submitted to ERP via ordermatic / total orders received by the customer from all channels in that week)
  • Leading: >70% of new orgs copy their email alias during first session
  • Leading: % of orgs with >0 email-sourced orders within 2 weeks of redesign
  • Secondary: p50 time from work item creation to ERP submission < 5 minutes (for reviewed orders)
  • Secondary: >50% of new orgs complete onboarding checklist in first session
  • Tertiary: Support tickets about β€œhow to set up email” drop to near-zero

The denominator (β€œtotal orders received by the customer from all channels”) cannot be measured automatically β€” ordermatic doesn’t have visibility into orders that never touch the system. Measurement approach, in order of reliability:

  1. ERP order count comparison (preferred): Query the customer’s ERP for total orders created in the period, then compare to ordermatic-submitted orders. Requires ERP read access, which the connectors project already provides for Prophet21 and NetSuite. Coverage = ordermatic submissions / ERP total orders. This is the most accurate source.
  2. Customer self-report (onboarding): During onboarding, ask β€œHow many orders does your team process per week?” Store as estimatedWeeklyVolume on the org. Coverage = ordermatic submissions / estimated volume. Useful for orgs where ERP query isn’t available yet.
  3. Trend-based proxy (fallback): Track ordermatic submissions over time. If submissions plateau while the customer reports growing volume, coverage is declining. This is directional, not precise.

For the dashboard β€œcoverage” stat, use approach #1 when ERP data is available, fall back to #2. Display the data source: β€œCoverage: 87% (from ERP)” vs β€œCoverage: ~85% (estimated)”. If neither is available, hide the coverage metric rather than showing a meaningless number.

  • Run as a Temporal workflow (not a DB migration)
  • Process in batches of 1000 emailEvents ordered by createdAt DESC (newest first)
  • For each emailEvent with providerMetadata.ordermaticInbox: create a workItem, copy inbox state
  • Idempotent: check if workItem with matching emailEventId exists before creating
  • No downtime. Old UI reads emailEvents, new UI reads workItems. Both sources are consistent after backfill.

Backfill sizing (must verify before running): Run this query in staging before scheduling:

SELECT clerk_organization_id, COUNT(*) as total,
COUNT(*) FILTER (WHERE provider_metadata->'ordermaticInbox' IS NOT NULL) as inbox_events
FROM email_events GROUP BY clerk_organization_id ORDER BY total DESC;

Current estimate: ~50k emailEvents per org Γ— ~10 active orgs = ~500k rows total. At 1000/batch with ~50ms per batch (single INSERT with ON CONFLICT DO NOTHING), estimate ~25 minutes. But this MUST be validated against production row counts before scheduling. If total exceeds 1M rows, reduce batch size to 500 and add a 100ms delay between batches to avoid saturating DB connections. Backfill runs with READ COMMITTED isolation (not serializable) to minimize lock impact on production reads.

  • Both emailEvent inbox state (providerMetadata) AND workItem are written in a single DB transaction
  • This ensures atomicity β€” no inconsistency between the two stores
  • Dual-write code paths exist during Phases 1-7. Removed in Phase 8 cleanup.
  • If workItems write fails within the transaction, the entire transaction rolls back (including emailEvents update)

Lock contention mitigation: The email webhook is the hot path (~100-500 writes/day across all orgs currently). The dual-write adds one INSERT to the transaction that already UPDATEs emailEvents. At current scale this is negligible. If webhook p99 latency exceeds 500ms after dual-write ships, the mitigation path is: (a) benchmark the INSERT overhead in staging, (b) if significant, decouple the workItem write into an async post-commit hook (Drizzle afterInsert -> queue -> workItem INSERT). This trades atomicity for latency: if the app crashes between commit and hook execution, the workItem can be missing until reconciliation. During backfill there is also a race where backfill may insert a workItem before the async hook runs; the UNIQUE constraint prevents corruption but duplicate-key errors must be treated as idempotent success. Async workers should use retry with exponential backoff plus upsert-or-ignore semantics to suppress noisy retries. Optionally limit backfill to emailEvents older than a configurable cutoff (for example created_at > 1h) to avoid the hottest window. Monitor via /api/webhook/inbound-email p99 request duration before enabling the async path.

  • Feature flag unified_inbox_v2 OFF β†’ old UI, reads emailEvents (still being written via dual-write)
  • workItems table is additive β€” can be dropped with no impact on old UI
  • Old routes continue to function when flag is off
Phase 1 (workItems table)
β”œβ”€β”€β–Ί Phase 2 (generalize UI) ──► Phase 3 (split pane) ──┐
└──► Phase 4 (auto-archive + upload + snooze) ───────────
β”œβ”€β”€β–Ί Phase 7 (nav + delight)
Phase 5 (SLA + dashboard) ◄── Phase 2 β”‚ ──► Phase 8 (rollout)
Phase 6 (onboarding) [independent] β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Phases 3 and 4 can run in parallel after Phase 2. Phase 5 requires Phase 2 (needs workItems queries). Phase 6 is independent and can start anytime. Phases 7 and 8 are sequential and come last.

  1. workItems table + dual-write β€” Schema migration, Temporal backfill workflow, dual-write in /api/webhook/inbound-email + upload handlers
  2. Generalize Inbox UI β€” InboxEmailRow β†’ InboxItemRow, queries switch to workItems, bulk-actions generalized
  3. Full-width detail editing β€” Extract DocumentOrderReview as embeddable React component, full-width layout (list hides, PDF left + form right)
  4. Auto-archive + upload integration + snooze β€” ERP webhook triggers archive, upload creates workItem with auto-assign, Temporal scheduled workflow for unsnooze
  5. SLA timers + team dashboard β€” Server-rendered SLA from timestamps (not client clock), aggregation queries cached via stale-while-revalidate at the API endpoint level (5-min TTL, in-memory per instance is acceptable at current scale; move to Redis if multi-instance inconsistency becomes an issue). PDF viewer uses existing PDFViewerWithHighlights.tsx (react-pdf based, already supports zoom + page nav + bounding box highlights).
  6. Onboarding flow β€” Email alias hero empty state, checklist component, β€œsend test order” with rate limit (3/hour)
  7. Navigation consolidation + delight touches β€” Sidebar simplification, route redirects, D1-D5 delight items
  8. Feature flag rollout + cleanup β€” Enable for new orgs β†’ beta customers β†’ all orgs. Then: remove dual-write code, remove old pages (orders/index, review-pending-documents, inbox, dashboard), remove providerMetadata inbox state, remove feature flag

Phases 2, 3, and 7 are frontend-heavy (generalized row component, split-pane layout, keyboard UX, delight touches). Combined frontend effort estimate: L (human: ~2-3 weeks / CC: ~2-3 days). Consider a dedicated frontend spike for the split-pane layout before committing to Phase 3. Per-feature effort in scope table uses T-shirt sizing (S/M/L) for individual features. The aggregate note refers to combined frontend effort across multiple phases β€” not a contradiction.

Elapsed time badge on each row replaces the date column when SLA is configured:

  • Green (<50% of SLA threshold): text-green-700 bg-green-50 β€” e.g., β€œ2h 15m”
  • Amber (50-90% of SLA): text-amber-700 bg-amber-50 β€” e.g., β€œ6h 02m”
  • Red (>90% of SLA): text-red-700 bg-red-50 font-medium β€” e.g., β€œ8h 45m”
  • Breached (>100% of SLA): text-red-700 bg-red-50 font-bold + ⚠ icon β€” e.g., β€œ12h 30m βš β€ When no SLA threshold is configured for the org, show the regular date (no timer). Timer is server-rendered from receivedAt and org threshold β€” not a client countdown.
FeatureLoadingEmptyErrorSuccessPartial
Inbox list8 skeleton rows pulsingOnboarding hero with email alias”Couldn’t load inbox” + retryRows renderNew item arrives between refreshes
Detail (PDF)PDF skeleton + spinner”No PDF attached” for manual items”PDF failed to load” + retryPDF rendersPDF loads, form still loading
Detail (form)Form skeletonPre-filled from extraction”Extraction failed” + retryForm populatedSome fields low-confidence (amber highlight)
Submit to ERPButton disabled + spinnerN/A”Submission failed: [ERP error]” + retrySuccess toast + auto-archiveN/A
SLA timer”—” placeholderNo threshold β†’ hide timerN/A (computed)Green/amber/red badgeN/A
SnoozeInstant (optimistic)N/A”Couldn’t snooze” toast”Snoozed until [time]” toastN/A
DashboardStats bar shimmer”No data for this period""Dashboard unavailable” + retryStats + team table renderStale data (cache TTL note)
OnboardingN/AEmail alias hero + copy buttonN/AFirst order β†’ list viewAlias copied, no order yet
Send test order”Sending…” spinnerN/A”Couldn’t send” toast”Test order sent! Check inbox”N/A
Bulk actions”Updating X items…”Disabled when 0 selected”X of Y failed” toast”Updated X items” toastSome items failed
Clone”Cloning…” spinnerN/A”Clone failed” toast”Cloned to your inbox” toastN/A

Sidebar simplifies to 4 items: Inbox (with unread badge), Customers, Products, Settings. Dashboard becomes a manager tab within Inbox. /dashboard redirects to /inbox?view=dashboard. /orders, /orders/new, /inbox all redirect to /inbox.

Full-width detail replaces list (not side-by-side split pane). Clicking a row navigates to full-width view with PDF viewer (left 50%) + order review form (right 50%). Back button or Escape returns to list. j/k navigates between items in detail view (pre-fetches adjacent items). Cmd+Enter submits to ERP. On screens <1024px, PDF and form stack vertically.

LIST VIEW (default) DETAIL VIEW (on row click)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Toolbar + filters β”‚ β”‚ ← Back to Inbox [Snooze] β”‚
β”‚ ───────────────── β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚ Row 1 β”‚ ──► β”‚ β”‚ PDF β”‚ Order Review β”‚β”‚
β”‚ Row 2 (focused) β”‚ β”‚ β”‚ Viewer β”‚ Form β”‚β”‚
β”‚ Row 3 β”‚ β”‚ β”‚ β”‚ β”‚β”‚
β”‚ ... β”‚ β”‚ β”‚ β”‚ [Cmd+Enter] β”‚β”‚
β”‚ Pagination β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • Desktop (>1024px): Full experience. Sidebar + list view as default. Click row β†’ list hides, full-width detail (PDF left + form right, side-by-side). Back/Escape returns to list. Keyboard shortcuts active. Drag handle on PDF/form split within detail view.
  • Tablet (768-1024px): Sidebar collapses to drawer. List view is default. Tap row β†’ full-width detail with PDF stacked above form (vertical layout). Touch targets 44px minimum.
  • Mobile (<768px): Bottom nav (4 icons: Inbox, Customers, Products, Settings). List-only view. Tap row β†’ detail (stacked). No keyboard shortcuts. Simplified toolbar (search + filter icon).
  • Keyboard: j/k (navigate), Enter (open), Escape (back to list), e (archive), ? (help)
  • ARIA: role="listbox" on inbox list, role="option" on rows, aria-selected on focused row
  • Screen reader: Each row announces β€œOrder from [sender], [subject], status [status], [SLA time]”
  • Color contrast: All status badge colors meet WCAG AA (4.5:1 minimum)
  • Focus indicators: ring-2 ring-[var(--color-navy)] on focused elements (already in InboxEmailRow)
  • SLA colors: Red/amber/green are supplemented with text labels (not color-only communication)

After Cmd+Enter submits an order to the ERP:

  1. Green flash on detail view border (300ms, border-green-500)
  2. Auto-advance to next unprocessed item in the list (pre-fetched)
  3. If no more items: return to list with β€œAll caught up!” empty state This is the core flow-state pattern β€” submission feels instant and the user is already reviewing the next order before they consciously decided to move on.

Dashboard is a tab within Inbox (visible to users with hasElevatedInboxAccess()). Layout: compact stats bar + team table, no charts.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Today: 47 orders Avg: 4m 12s SLA: 94% 87% β”‚
β”‚ coverage β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ TEAM ACTIVE DONE AVG TIME BREACHES β”‚
β”‚ Sarah K. 8 12 3m 45s 0 β”‚
β”‚ Mike R. 5 9 5m 02s 1 β”‚
β”‚ Priya S. 3 15 2m 30s 0 β”‚
β”‚ (unassigned) 12 β€” β€” 3 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Stats bar: 4 numbers in a row, text-2xl font-bold with label below in text-xs text-gray-500. Team table: sortable by any column. Clicking a name filters the inbox to that person’s items. β€œ(unassigned)” row always at bottom in amber if breaches > 0.

”All Caught Up” Empty State (from design review)

Section titled β€œβ€All Caught Up” Empty State (from design review)”

When all active items are processed/archived:

  • Centered checkmark icon (green)
  • Headline: β€œAll caught up!”
  • Subtext: β€œNo orders need your attention right now.”
  • Secondary action: β€œView archived orders” link Different from the onboarding empty state (which shows email alias hero for new orgs).

Dropdown with quick presets + custom option:

  • β€œLater today” (4 hours from now)
  • β€œTomorrow morning” (next business day 9am)
  • β€œNext week” (next Monday 9am)
  • β€œPick date & time…” (date/time picker) Available as hover action on rows and as button in detail view header.

Inline banner at top of inbox list (not floating widget). Collapses as steps complete. Dismissible after all steps done. Persisted per-org (not per-user).

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Get started with ordermatic β”‚
β”‚ β”‚
β”‚ βœ“ Connect your ERP β”‚
β”‚ 2. Copy your email address: β”‚
β”‚ orders@acme.ordermatic.io [Copy] β”‚
β”‚ 3. [Send a test order] β”‚
β”‚ β”‚
β”‚ Or drop a PDF here to upload β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β€œSend test order” sends a real email with a sample PO PDF to the org’s alias. The order flows through the full pipeline (email β†’ triage β†’ extract β†’ workItem). The user sees it appear in their inbox within seconds β€” this IS the aha moment. Rate limit: 3 test orders per hour per org.

Triggered by ? key. Uses existing Modal component (size md). Two-column layout:

Navigation Actions
j / k Next / previous e Archive
Enter Open item Shift+3 Delete
Escape Back to list u Mark unread
v Set to "in review"
Processing Cmd+Enter Submit to ERP
? This cheatsheet s Snooze

Enable dual-write FIRST, then run backfill. This way:

  • New items are captured by dual-write immediately
  • Backfill fills historical items
  • The idempotency check (UNIQUE constraint on emailEventId) prevents duplicates if both write the same item
  • No race condition possible because the constraint is DB-enforced, not application-level
ReviewTriggerWhyRunsStatusFindings
CEO Review/plan-ceo-reviewScope & strategy1CLEARSCOPE_EXPANSION mode, 0 critical gaps
Codex Review/codex reviewIndependent 2nd opinion1PASS3 findings, 3/3 fixed
Eng Review/plan-eng-reviewArchitecture & tests (required)1CLEAR4 issues resolved, 0 critical gaps
Design Review/plan-design-reviewUI/UX gaps1CLEARscore: 5/10 β†’ 8/10, 8 decisions
  • CODEX: Fixed extraction/submission workflow separation, desktop responsive alignment, dashboard interaction states
  • UNRESOLVED: 0 across all reviews
  • VERDICT: CEO + ENG + DESIGN + CODEX CLEARED β€” ready to implement