Cross-System Record Reconciliation Checker
Salesforce action: GetRecordByExternalID. Scheduled flow iterates records in Dataverse (the source system), looks up each by external ID in Salesforce, flags mismatches or missing records, and emails a reconciliation report to admins.
Provided as-is, without warranty of any kind. Review and test each pattern in a non-production environment before deploying it to live automations. See our Terms.
Overview
A scheduled reconciliation flow that treats Dataverse as the source of truth and verifies that each row has a matching record in Salesforce. It runs daily, iterates every row in the configured Dataverse table, looks up the corresponding Salesforce record by external ID, and flags two kinds of exceptions: Missing (no Salesforce record exists for the external ID) and Mismatch (the Dataverse display name does not equal the Salesforce record's Name). A single HTML report is emailed to admins so data-integrity issues between the two systems surface the next morning instead of during a quarter-end audit.
Use Case
Multi-system environments (MDM patterns, CDP backends, hybrid CRM deployments) frequently drift between Dataverse and Salesforce because each system has its own maker tools. Manual reconciliation is slow, inconsistent, and usually too late. This flow turns reconciliation into a daily background job powered by standard connectors, so Admins and Developers see a full exception list in their inbox without running SOQL or fetchXML by hand.
The flow is ideal for teams that:
- Salesforce is the customer system of record but Dataverse is the source for a subset (e.g. field-service accounts).
- A nightly ETL writes Dataverse but Salesforce receives writes from a different integration, so drift is expected.
- Audit/compliance requires a daily "are-we-in-sync" log without granting integrators direct DB access.
Flow Architecture
Recurrence_Daily_7am
RecurrenceRuns every day at 07:00 Eastern Time.
Init_varMismatchRowsHtml
Initialize variable (String, "")Accumulator for exception HTML rows appended during the loop.
Init_varTotalProcessed
Initialize variable (Integer, 0)Count of Dataverse rows iterated.
Init_varMismatchCount
Initialize variable (Integer, 0)Count of mismatches (name differs).
Init_varMissingCount
Initialize variable (Integer, 0)Count of Dataverse rows with no matching Salesforce record.
Init_varReportRecipient
Initialize variable (String)Pulled from `flowlibs_ReconciliationReportRecipient` env var.
Init_varSalesforceObject
Initialize variable (String)Pulled from `flowlibs_SalesforceObjectType` (e.g. `Contact`).
Init_varSalesforceExtIdField
Initialize variable (String)Pulled from `flowlibs_SalesforceExternalIdField` (e.g. `External_Id__c`).
Init_varDataverseSourceTableSet
Initialize variable (String)Pulled from `flowlibs_DataverseSourceTableSet` (display/reporting only).
Init_varDataverseExternalIdColumn
Initialize variable (String)Dataverse column holding the external key (e.g. `emailaddress1`).
Environment Variables
| Schema name | Type | Default | Description |
|---|---|---|---|
| flowlibs_ReconciliationReportRecipient | String | you@yourcompany.com | Who gets the daily report. Semicolon-separated emails supported. |
| flowlibs_SalesforceObjectType | String | Contact | Salesforce object/table to reconcile against. |
| flowlibs_SalesforceExternalIdField | String | External_Id__c | API name of the Salesforce External ID field used for lookup. |
| flowlibs_DataverseSourceTableSet | String | contacts | Entity set name of the Dataverse source table (shown in report header). |
| flowlibs_DataverseExternalIdColumn | String | emailaddress1 | Dataverse column that holds the external key passed to Salesforce. |
| flowlibs_DataverseDisplayNameColumn | String | fullname | Dataverse column used as the "display name" compared against Salesforce `Name`. |
Connectors & Connections
| Connector | API name | Actions used |
|---|---|---|
| Microsoft Dataverse | shared_commondataserviceforapps | ListRecords (Pulls every row from the source Dataverse table.) |
| Salesforce | shared_salesforce | GetItemByExternalId (Looks up the Salesforce record by external ID for each Dataverse row.) |
| Office 365 Outlook | shared_office365 | SendEmailV2 (Sends the reconciliation HTML report.) |
Note — All connections are referenced as solution connection references; the flow is portable between environments as long as a connection is mapped at import time.
Customization Guide
Almost every realistic variant of this flow can be implemented by changing environment variable values. A few cases require small edits inside the flow definition — those are called out explicitly below.
- Re-target a different Salesforce object (e.g. Account)
- Update flowlibs_SalesforceObjectType to Account and flowlibs_SalesforceExternalIdField to whatever field on Account is marked as External ID (e.g. AccountNumber__c). No flow edits required — the Salesforce action reads both from variables bound to env vars.
- Re-target a different Dataverse source table (e.g. accounts)
- Update flowlibs_DataverseSourceTableSet to accounts (display/reporting only), and set flowlibs_DataverseExternalIdColumn / flowlibs_DataverseDisplayNameColumn to the matching account columns (e.g. accountnumber and name). Also edit the List_Dataverse_Source_Records action's entityName parameter from contacts to the new entity set name — it is hard-coded per the FlowLibs "Zero Flow Checker errors" rule.
- Compare more than just display name (e.g. phone, email)
- In If_Record_Mismatch_Found, replace the single not(equals(...)) expression with an or containing one not(equals(...)) per field you want to diff. Extend the mismatch row in Append_Mismatch_Row_To_Report to print each differing field.
- Change cadence
- Edit the trigger — frequency, interval, or schedule.hours / schedule.minutes
Key Expressions
The flow is intentionally light on Power Fx / WDL gymnastics — the heaviest expressions are the branch-name concatenation and the approval outcome check. They are listed below in the order they appear in the flow.
EXPR.01Dynamic Dataverse property access
Reads the external ID and display name columns from each Dataverse row using env-var-driven column names, so the same flow can target different tables without expression edits.
EXPR.02External ID coerced to safe string
Coerces the external ID to a string and defaults to empty before passing it into the Salesforce `GetItemByExternalId` path parameter.
EXPR.03Mismatch condition (case-insensitive, trimmed)
Used inside `If_Record_Mismatch_Found` — normalises both sides (coalesce → string → trim → lower) so whitespace and casing differences do not produce false-positive mismatches.
EXPR.04Fallback empty-body cell for the exceptions table
Renders a single "in sync" row when nothing drifted, otherwise dumps the accumulated mismatch rows into the report table.
EXPR.05Branch-on-action-outcome for Missing detection
No Scope wrapper needed — the `runAfter` does the real branching. The `equals: [1, 1]` sentinel just satisfies the If action's "expression required" rule.
Comments
Sign in to join the conversation.
Sign inNo comments yet. Be the first to share your experience with this flow.