Skip to content

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 sf CLI, not stored client credentials. The vault client-credentials flow returned "no client credentials user enabled," so the project delegates auth to whatever org the local sf CLI is pointed at (sf org display --target-org LITIFY_ORG --json). No SF secrets live in the project. Tradeoff: the sync can only run where sf is 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 --backfill safe 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 sfUserId set where known; the sync sets OwnerId on 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

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 --confirm deletes every Note with External_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, Intake a0CUV00006LMO7s2AH, Matter a0LUV00000G5NHJ2A3.
  • 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; check reports/ for the final counts. Next steps: (1) build the phase-2 LWC chat-bubble conversation view on Matter and Intake page layouts that reads litify_pm__lit_Note__c records where Topic is Text message sent or Text message received, grouped by counterparty and ordered by CreatedDate; (2) wire node src/sync.js --incremental to launchd for the hot tier poll (see pollWindows in config/sync-config.json); (3) promote to wave2_case_managers by setting activeWave: "wave2_case_managers" and running --backfill. Rollback for anything is node src/rollback.js --wave=<name> --confirm. Auth: RC JWT in .credentials/rc-credentials.json, SF via sf org display --target-org LITIFY_ORG.

Sources

See also