01Two demos at a glance
Both demos are live on portal.hmrdesigns.com. No setup before either. Diego can present them together in one call, or split across two.
Real-time bidi sync
Event / contact / venue changes flow both directions between portal CRM and HMR DEV via the "Sync now" button. Designer-initiated.
- Edit anywhere → push the button → see it on the other side
- ~10 minutes
Stage automation + followup ladder
Three acts: HMR overrides CRM stage moves it can't yet support; HMR flag changes pull forward into CRM; inquiry leads that sit too long trigger reminder emails.
- Act A: drag stage past HMR support → revert
- Act B: flip HMR flag → CRM follows
- Act C: aged inquiry lead → reminder email
- ~10 minutes total
Prod is pinned with the right flags + recipient overrides in place. All followup emails route to hkhurshid@datastudios.ai only — designers and Mal never see them during the demo. The EventBridge cron is intentionally NOT enabled, so nothing fires automatically; Demo 2C is triggered on demand.
Field edits (event/venue/contact details — name, dates, budget, phone, etc.) are bidirectional via the periodic sweep. Change on either side, then "Sync now" reconciles. The exception is event-field edits on the CRM side, which fire a background push to HMR right after save — but for demo purposes it's safe to think of all field edits as sweep-driven both ways.
Stage changes from the CRM (drag a kanban card) are the one immediate path. The HMR-authority check runs as a background task right after your drag, so Demo 2A's revert lands within seconds — no Sync now needed.
Stage changes from HMR (someone flips closed=true in HMR Direct) follow the field-edit rule: they wait for Sync now (or the periodic sync cron, frequency TBD — likely 15 min or so). That's why Demo 2B includes a Sync now step.
02Pre-flight
Two browser tabs and a sanity check on the push-target banner. That's it.
Open these two tabs
-
Portal CRM —
https://portal.hmrdesigns.com/crm/pipelineLogin as the demo user. The kanban + event detail pages are where most of the action happens. -
HMR DEV — the staging HMR Direct URL Diego uses
Side-by-side with the portal makes the round-trips obvious.
The orange banner at the top of every CRM page reads PUSH TARGET: HMR DEV. If it doesn't, stop and tell Haziq — that would mean prod is pointed at HMR PROD which is a separate problem. Every demo write goes to HMR DEV; HMR PROD is untouched until the formal cutover (months out).
03Demo 1 · Real-time bidi sync
The "I changed it over there, it changed here" demo. Three round-trips in ~8 minutes: event field, venue field, contact field. Each one starts in one system and the user watches it land in the other.
The wow moment
Open HMR DEV in one tab, portal CRM in another. Edit something in HMR. Click Sync now on the portal banner. The sweep walks every event / contact / venue so completion time scales with how much HMR has changed since the last run — usually a minute or two, occasionally longer on a busy day. Then reverse the direction — edit in portal, push to HMR, refresh HMR to confirm.
"Sync now" is a synchronous sweep across all bidi-synced records, not a single-record push, so the banner stays in the "Syncing…" state until the whole pass finishes. That's typically under a minute or two, but can run longer when there are many recent HMR-side changes to pull. Per-card progress is a polish item we can add later — for now the banner just clears when the sweep is done and the page reflects the new state on next interaction.
Walkthrough
-
Open the portal CRM and HMR DEV side-by-side.
Portal:
localhost:5900/crm/pipeline· HMR DEV:https://dev.hmrdesigns.com -
Pick a non-production event you can mutate.
Anything with both an HMR id (so it round-trips) and no risk of being mistaken for a live job. Demo events from prior sessions work.
-
In HMR DEV, make one edit on each of the three entities tied to this event:
- Event: change a top-level field (e.g. event name, end date, budget).
- Venue: change a venue field (e.g. address, phone).
- Client: change a contact field (e.g. name, phone).
Three edits across three entity types proves the sync isn't just touching one table. -
In the portal, click the "Sync now" button in the push-target banner at the top of the CRM.
Banner shows "Syncing…" while the sweep walks all records (typically a minute or two; longer if many records changed on the HMR side). When it collapses, refresh the event page — all three HMR-side edits (event field, venue field, client field) now show in the portal.
-
Now reverse — edit the same three entities from the portal side:
- Event: inline-edit a top-level field on the Event Detail page.
- Venue: open the venue's drawer / detail and inline-edit a field there.
- Client: open the client's drawer / detail and inline-edit a field there.
Each save fires a background HMR push. After "Sync now" completes (or after a brief wait), refresh HMR DEV — all three portal-side edits are reflected on the HMR side. -
Drag a kanban card from Inquiry → Qualified in the portal.
CRM-side stage move; the existing HMR event-creation flow runs in the background (idempotency key prevents double-creates on retry). Stage history row written with
source='user'.
What's intentionally invisible
No admin sync panel. No "failed syncs" page. No retry buttons on cards. Engineering catches failures via SNS email alerts and resolves them before a designer sees anything broken. Mal should see polish, not plumbing.
04Demo 2A · Stage authority revert
The "HMR is the source of truth for stage" demo. Drag a card past where HMR allows; watch it pop back; show the audit trail explaining why.
Setup
Pick a Qualified event that's already pushed to HMR DEV. Confirm in HMR DEV that the event's confirmation_id is empty — that's what makes "Confirmed" unsupported.
Walkthrough
-
In the portal kanban, drag the card from Qualified → Confirmed.
Card visually lands in the Confirmed column. PATCH succeeds.
-
Wait about 5 seconds.
Card pops back to Qualified on its own. A banner appears on the event detail page: "Stage reverted by HMR sync — CRM stage 'confirmed' has no HMR support; reverting to qualified."
-
Open the event's History tab.
Two rows back-to-back: the user's move with
source='user', then the auto-revert withsource='hmr_authority_revert'. Italic, "reverted by sync" label — visually distinct from a user-driven move. -
Now flip it the right way: in HMR DEV, set the event's
confirmation_idto a real value. -
In the portal banner, click "Sync now".
No need to drag the card again — the sweep notices HMR now derives "confirmed" for this event and advances the kanban card automatically. A new history row appears:
qualified → confirmedwithsource='hmr_sync'. (You could also drag the card manually — it would stick this time since HMR supports it — but Sync now demonstrates the more useful direction: HMR-driven advances catch the CRM up without anyone touching the kanban.)
Talking point for Mal
"HMR is authoritative for what stage a lead can be in. The CRM lets you drag freely, but if HMR's flags don't back it up, the system rolls you back and writes an audit row so you know why. No silent failures — and no manual cleanup if a stage move was premature."
05Demo 2B · HMR-driven forward transition
The mirror of 2A. When HMR's flags advance (e.g. someone closes an event in HMR Direct), the next sync pulls that change into the CRM automatically — no kanban drag required.
Setup
Pick a Confirmed event in the CRM. Same event from Demo 2A works fine — leave it at Confirmed.
Walkthrough
-
In HMR DEV, set
closed=trueon the event.This is the HMR-side action a real designer would do when wrapping up. -
In the portal banner, click "Sync now".
Banner shows "Syncing…" while the sweep walks records (usually a minute or two).
-
When the sweep finishes, refresh the kanban.
The card has moved from Confirmed to Closed automatically. No manual drag.
-
Open the event's History tab.
A new row:
confirmed → closedwithsource='hmr_sync'. Distinct from a user move and from a revert.
Talking point for Mal
"The legacy lambdas only pulled flag changes from HMR to Nutshell on a delay. Now the kanban reflects HMR state on demand — Mal flips a flag, Diego clicks Sync now in the next meeting, the card has moved. No more 'wait, why is this still in Confirmed when I marked it Closed yesterday?'"
06Demo 2C · Followup email ladder
The "system babysits stale leads" demo. A lead in Inquiry for 10+ days triggers a reminder email; 21+ days a second reminder; 30+ a final warning; 35+ auto-Dead. For the demo we trigger the runner on-demand — the periodic cron is intentionally not running in prod yet.
Every email from the runner is routed to hkhurshid@datastudios.ai only, by prod env config. The override clears CC too. You can show Diego an actual email landing in Haziq's inbox during the call without any risk of real recipients getting hit.
Setup
Find a lead in Inquiry that's been there 10+ days. If none exist (or you want a clean one), backdate an Inquiry stage_history row on a safe test event:
# From the prod DB (psql against dev RDS):
INSERT INTO crm.stage_history (event_id, to_stage, source, occurred_at)
VALUES ('<test_event_id>', 'inquiry', 'migration', NOW() - INTERVAL '11 days');
Walkthrough
-
Open the lead in the portal.
Show its History tab — the Inquiry entry says 11+ days ago.
-
Haziq triggers the followup runner on the prod EC2.
Via SSM session manager (no SSH key needed):
aws ssm start-session --target i-00b819f124a04aa32 \ --region us-east-2 --profile hmr-designs # then on the instance: cd /opt/hmr-portal && sudo -u ec2-user bash -c \ 'source venv/bin/activate && python -m scripts.followup_run --json'JSON summary:total_inquiry_events,processed,d10_sent: 1, plus the per-event breakdown. The runner picks up the env from the systemd unit so all 6 flags are already on. -
Check Haziq's inbox.
An email lands within ~30 seconds — subject like "Reminder: this lead has been in Inquiry for 10 days." Body has the lead name + a deep link to the event in the portal.
-
Trigger the runner a second time, same day, same event.
Response shows the event skipped (
skipped_already_sent: 1). Idempotency hit — no duplicate email lands in Haziq's inbox.
Talking point for Mal
"Right now the runner is wired up and the gate is on, but the daily cron isn't enabled yet — that's the last switch we flip before real designers start getting these emails. We've built the recipient override in for the demo so everything routes to Haziq's inbox; that way we (and you) can review exactly what would go out and to whom before the cron is enabled in production."
07What's pre-set on prod
For Diego's reference. Nothing here needs adjusting before the demo — it's already on the EC2. Useful only if something behaves unexpectedly during the call.
| Flag | Prod value | Effect |
|---|---|---|
| STAGE_AUTHORITY_REVERT_ENABLED | true | Drives Demo 2A — CRM moves past HMR support are reverted. |
| STAGE_SYNC_AUTO_TRANSITION_ENABLED | true | Drives Demo 2B — HMR flag changes pull forward into CRM. |
| FOLLOWUP_AUTOMATION_ENABLED | true | Drives Demo 2C — runner does real work when invoked manually. |
| FOLLOWUP_SEND_REAL_EMAILS | true | Runner sends actual SES emails (not just logs). |
| FOLLOWUP_RECIPIENT_OVERRIDE | hkhurshid@datastudios.ai | Safety override — every followup email goes to Haziq only. Designers + Mal never receive emails during demos. Removed at prod cutover. |
| SYNC_PUSH_EDIT_ENABLED | true | Enables CRM → HMR push for inline edits (Demo 1's reverse direction). |
The EventBridge cron rule for the followup runner is not yet created on the AWS account. So no automated firings — Demo 2C only runs when someone manually invokes the script. At prod cutover, the cron gets created + enabled at a periodic cadence (15 min or different, TBD with Mal), and FOLLOWUP_RECIPIENT_OVERRIDE gets removed so real designers receive their reminders.
08Engineering safety net
Designers don't see failures; engineering does. Here's where to look if something goes sideways during the demo.
| Channel | What it carries | How to read it |
|---|---|---|
| SNS email · hmr-sync-alerts-{dev,prod} | Sync failures, dead-letter trips, lag warnings, HMR API shape canary alerts | Gmail filter on [hmr-sync-alerts] subject prefix; lands in Haziq (dev) / Haziq + Diego (prod) inboxes |
| crm.sync_state table | Per-record sync status, retry count, last-attempt timestamp | psql -c "SELECT * FROM crm.sync_state WHERE status='dead_letter' LIMIT 10" |
| crm.sync_log table | Per-attempt audit trail with HMR response bodies | psql -c "SELECT * FROM crm.sync_log WHERE event_id = <id> ORDER BY created_at DESC LIMIT 20" |
| crm.email_log table | Per-followup-email record with SES message id, template, recipient | psql -c "SELECT * FROM crm.email_log ORDER BY sent_at DESC LIMIT 20" |
| uvicorn log · /tmp/uvicorn.log | Live request log + stack traces from any unhandled exception | tail -f /tmp/uvicorn.log |
Force-retry a stuck sync
PYTHONPATH=. venv/bin/python scripts/sync_force_retry.py <record_id>
Manually run the followup runner on prod (Demo 2C trigger)
# SSM session to prod EC2 (no SSH key needed)
aws ssm start-session --target i-00b819f124a04aa32 \
--region us-east-2 --profile hmr-designs
# then on the instance — env vars come from /etc/hmr-portal.env
cd /opt/hmr-portal && sudo -u ec2-user bash -c \
'source venv/bin/activate && python -m scripts.followup_run --json'
09Troubleshooting
Specific things to try if a step doesn't behave as expected during the live demo.
"Sync now" button does nothing visible
- Check the network tab for the
POST /api/crm/admin/sync/sweepresponse. 200 = sweep ran; check the JSON body for per-record outcomes. - Check uvicorn log. Look for HMR client errors (timeouts, auth failures). Common: HMR creds expired in Secrets Manager.
- Check
crm.sync_state. If the event is indead_letter, the sweep won't retry — usesync_force_retry.py.
Stage drag reverts immediately instead of waiting for the sweep
That's the inline-reconcile BackgroundTask doing its job — added in HD-171 v2 for sub-second feedback. If you want to demo the sweep-driven revert path explicitly, set STAGE_RECONCILE_INLINE_ENABLED=false temporarily before the demo. Most demos benefit from the snappy inline behavior.
Followup runner reports a lead with no inquiry_cycle_start
The lead has stage='inquiry' but no stage_history row with to_stage='inquiry'. Backfill (note: source must be a valid crm.stage_history_source enum value — use 'migration'):
INSERT INTO crm.stage_history (event_id, to_stage, source, occurred_at)
VALUES ('<event_id>', 'inquiry', 'migration', NOW() - INTERVAL '15 days');
Event create fails with "HMR has no eventtype matching …"
Our crm.events.event_type enum doesn't 1:1 map to HMR's eventtype catalog. If you create an event with an event_type HMR doesn't have, HMR rejects, the BackgroundTask sets creation_state='failed_validation', and the stage is auto-rolled-back to inquiry. Check the failure reason:
PYTHONPATH=. venv/bin/python -c "
from api.auth.database import get_session_factory
from sqlalchemy import text
S = get_session_factory(); db = S()
r = db.execute(text(
'SELECT creation_state, creation_failure_category, creation_failure_reason '
'FROM crm.events WHERE id = :id'
), {'id': '<event_id>'}).mappings().first()
print(dict(r))"
Fix the event_type to a HMR-recognized value, reset creation_state='idle', and re-PATCH the stage.
Demo email lands in spam
SES from analytics@hmrdesigns.com needs SPF + DKIM aligned for hmrdesigns.com. If recipient is gmail and lands in spam, check the message headers for SPF=pass / DKIM=pass. DNS records live in the HMR Designs domain registrar.