RingCentral ↔ Litify SMS Sync¶
Status: Active · wave1_intake backfill complete, phase 2 pending Project: Litify · Operations Owner of record: Sam
What this is¶
An autonomous one-way sync that mirrors every RingCentral SMS into
Litify as a litify_pm__lit_Note__c record on the matching Matter or
Intake. It replaces the deprecated SMS-Magic package with a local
Node.js service that Sam (or staff) can run on demand, will eventually
run on a polling schedule, and can be rolled back in a single command.
The sync is built at /Users/samaguiar/Documents/Projects/litify/ringcentral-sms-sync/
and is the foundation for a phase-2 chat-bubble conversation LWC on the
Matter and Intake page layouts.
Why it was built¶
Two things were happening at once in April 2026: 1. SMS-Magic was being decommissioned. 5 permission-set assignments (SMS Converse + SMS Magic Custom) needed to go, and the data substrate that powered SMS conversations had to be rebuilt under something the firm owned end-to-end. 2. The phase-2 chat-bubble LWC needs a reliable Note-backed conversation view on the Matter and Intake page layouts — but only if Notes can be trusted as a complete record of every inbound and outbound SMS across the firm.
The sync solves both by reading RingCentral's
/restapi/v1.0/account/{acct}/extension/{ext}/message-store?messageType=SMS
endpoint, normalizing phone numbers to 10-digit US, and upserting Notes
by External_Id__c = rc_msg_{rcMessageId} for idempotency.
Architecture¶
src/rc-auth.js RC JWT bearer auth, 20-min token cache
src/sf-auth.js Reuses local sf CLI (sf org display --json)
No Connected App, no stored SF secret
src/rc-client.js Paginates message-store endpoint, normalizes records
src/phone-match.js 10-digit US normalization + SOQL LIKE matcher
on Matter_Phone__c, Client_Phone__c,
litify_pm__Phone__c, Phone_10_Digit__c
(open matters + unconverted intakes first)
src/sync.js Orchestrator: --dry-run, --backfill, --incremental,
--since, --until, --wave, --ext
src/rollback.js Deletes by External_Id__c LIKE 'rc_msg_%'
with wave/owner/date/ext-id filters (dry-run default)
config/sync-config.json Wave1–4 roster, phone fields, Intaker filter,
topic map, poll windows, test record ids
Key decisions¶
-
SF auth via
sfCLI, not stored client credentials. The vault client-credentials flow returned "no client credentials user enabled," so the project delegates auth to whatever org the localsfCLI is pointed at (sf org display --target-org LITIFY_ORG --json). No SF secrets live in the project. Tradeoff: the sync can only run wheresfis installed and authed, which is fine for Sam's Mac and fine for a future cron host. -
Upsert by
External_Id__c = rc_msg_{rcMessageId}for idempotency. Re-running any window is a no-op on already-synced messages. This is what makes--backfillsafe to rerun and what makes rollback surgical. -
Intaker SMS filtered out via body-prefix regex. Intaker's own pipeline already pushes those leads; letting this sync pick them up would duplicate them.
-
Sam's extension (115 / 502-813-8900) is the live test subject. A Sam Test Matter has his phone field populated, so any outbound text to that number from any RC ext matches it. No Alyssia-only pilot.
-
Topic picklist is unrestricted via API. "Text message sent" / "Text message received" both write successfully without a metadata deploy. Adding the values to the UI dropdown is phase-2 polish.
-
Wave config is the source of truth for owner mapping. Each extension row has
sfUserIdset where known; the sync setsOwnerIdon the Note so Litify views respect record ownership.
Validation results¶
| Run | Dataset | Result |
|---|---|---|
| Dry-run Alyssia ext 376557052, 60 days | 144 SMS | 26 Intaker filtered, 72 Matter matches, 20 Intake matches, 26 unmatched short codes, 0 errors, 64% match rate |
| Live 2-day window Alyssia | 20 SMS | 9 Notes written with correct OwnerId, topic direction, Matter linkage. 0 errors. |
| Idempotency re-run same window | 20 SMS | SF total held at 9. Upsert works end-to-end. |
| wave1_intake 60-day backfill (9 staff) | 1,020 SMS | 26 Intaker filtered, 742 Matter matches, 173 Intake matches, 79 unmatched short codes, 915 Notes upserted, 0 errors, 90% match rate |
Per-extension counts from wave1: Sam 1, Alyssia 144, JaNette 92, Kyle Haney 86, Timmera 98, Angelica 185, Chymez 144, Austin J 270, Kathy 0.
SOQL verification:
SELECT COUNT() FROM litify_pm__lit_Note__c
WHERE External_Id__c LIKE 'rc_msg_%'
-- 915
Current state¶
- wave1_intake backfill: done
- Live test on Sam's extension: passing
- Rollback script: tested, reversible
- SMS-Magic permission sets: removed (0 remaining)
- Phase-2 LWC (chat-bubble conversation view): not started
- Scheduled polling (launchd/cron): config set, not wired
- Webhook push via
SubscriptionWebhook: future replacement for polling - Waves 2–4 rollout: config ready, awaiting trigger
- Sara Collins: no RC extension in the directory; wave4 config flags and skips her automatically
Related sessions / context¶
The work ran in parallel with the broader RingCentral rollout and touched several adjacent systems:
- RingSense merge trigger in Salesforce handles RC call artifacts and Eve.legal integration for transcripts (separate project, complementary data source).
- SMS Ringcentral Project Job Breakdown — Nathaniel is responsible for the non-sync pieces of the rollout.
- Chat-bubble rewire assessment scoped the LWC build before this sync landed.
Rollback (every piece is reversible)¶
- Sync output:
node src/rollback.js --confirmdeletes every Note withExternal_Id__c LIKE 'rc_msg_%'. Filter by wave/owner/date/ single RC message id. - SMS-Magic perm set: re-assign via Setup → Permission Sets → Manage Assignments.
- Test records: delete Account
001UV00000kZGyjYAG, Intakea0CUV00006LMO7s2AH, Mattera0LUV00000G5NHJ2A3. - Project: the entire
ringcentral-sms-sync/folder is deletable. - RC JWT: revoke in the RC developer portal.
Pickup prompt for next session¶
Continue the RingCentral → Litify SMS sync work in
/Users/samaguiar/Documents/Projects/litify/ringcentral-sms-sync/. The wave1 backfill has run; checkreports/for the final counts. Next steps: (1) build the phase-2 LWC chat-bubble conversation view on Matter and Intake page layouts that readslitify_pm__lit_Note__crecords where Topic isText message sentorText message received, grouped by counterparty and ordered byCreatedDate; (2) wirenode src/sync.js --incrementalto launchd for the hot tier poll (seepollWindowsinconfig/sync-config.json); (3) promote to wave2_case_managers by settingactiveWave: "wave2_case_managers"and running--backfill. Rollback for anything isnode src/rollback.js --wave=<name> --confirm. Auth: RC JWT in.credentials/rc-credentials.json, SF viasf org display --target-org LITIFY_ORG.
Sources¶
- RingCentral → Litify SMS Sync built and wave1 backfill (2026-04-14)
- Build Brief: RingCentral SMS Chat-Bubble LWC for Matter and Intake (phase 2)
- Chat Bubble Rewire Assessment - 4.14.26
- SMS Ringcentral Project Job Breakdown For Nathaniel - 4.14.26
- RingCentral-Litify Sync: Script Optimization, Native Flow Discovery, Unlinked Tasks
- RC Migration Sync - 2026-04-13 (Blocked)
- RingSense Merge Trigger: Salesforce Deployment + Eve.legal Integration (March 7)