We automated dental recall and reactivation by installing a small, local Windows service inside the practice to read Dentrix via its API Program's ODBC connection, then syncing due and lapsed patients to a cloud CRM for SMS and email sequences with opt-out and dedupe controls. It now runs daily without anyone exporting lists. This guide shows the exact pattern we shipped and the gotchas we solved around Dentrix's closed, local-only design.
Dentrix recall automation is: a local service that safely pulls recall-due and lapsed patients from an on-prem Dentrix system, then routes them to CRM-driven reminders and reactivation sequences while keeping consent and contact data in sync.
The problem it solves
Most practices pulled recall lists out of Dentrix manually, cleaned them in Excel, and pasted emails into a bulk sender. That broke any time someone forgot to export last week's list, when preferred contact methods drifted, or when multiple team members each sent their own batch. In multi-location groups the problem multiplied because every office lived on its own Dentrix server with no public HTTP API.
| Manual recall process | Automated recall via local Dentrix connector |
|---|---|
| Staff runs Dentrix report, exports to Excel or PDF, cleans, dedupes, and uploads to a sender weekly | A local service reads Dentrix via program-issued ODBC and pushes only new due-lapsed patients on a schedule |
| Errors: stale phone numbers, wrong contact method, duplicate families, missed filters | Deterministic filters, idempotent push ledger, and validation before send |
| One-size-fits-all email from Outlook | CRM sequences by due-soon, due-now, and overdue, with SMS, email, and voice options |
| No audit trail or easy opt-out syncing back | Message log in CRM, opt-outs and bad numbers suppressed on future runs |
| Multi-location chaos: each office repeats the steps | One installer per office, standardized config, one central CRM |
How the automation works
We place a lightweight connector on the office server or a workstation that can reach Dentrix. Dentrix on-prem does not expose a public, internet-reachable API, so the connector authenticates locally using the Dentrix API Program's pattern: a program-issued credential grants an ODBC connection string to curated views or stored procedures. The connector polls on a schedule, computes the delta since the last successful run, then posts only new or changed patients into the practice's CRM where sequences run. When the practice used scheduled report exports instead of the API Program, the same connector watched a folder and parsed those files as the source of truth.
- Dentrix data source (local-only): Dentrix on-prem runs in-office. Integrations are local by design, with access controlled through Dentrix's API Program. HL7 is not available on Dentrix on-prem, and there is no public HTTP endpoint. This is why a small in-office service is required.
- Local connector service: A Windows service that authenticates using the program-issued connection, runs a read-only query against the curated recall view or stored procedure, and normalizes records into a minimal schema for CRM. It maintains an idempotency ledger so re-runs do not duplicate sends.
- CRM and sequencing: The connector posts patients into a CRM with tags and segments for due-soon, due-now, and overdue. Sequences handle SMS and email with opt-out capture and channel fallback. The CRM becomes the communication engine while Dentrix remains the clinical source of truth.
- Fallback via scheduled exports: Dentrix can schedule reports and tasks through its Batch Processor. When program access is not ready, we watch a folder for newly exported Excel or PDF reports and parse them to drive the same CRM flow.
- Monitoring and safety: Health pings, per-office logs, and a dry-run mode protect against accidental blasts. Opt-outs and bad numbers are suppressed on the next connector cycle.
Step-by-step: how to build it
1) Provision Dentrix API Program access and a local DSN
Request access through the Dentrix API Program for the practice. The model is local: integrations run inside the office and authenticate to curated views or stored procedures through a program-issued ODBC connection. Create a DSN on the server or workstation that the connector will use. If the program is not available yet, set up a scheduled recall export using the Batch Processor and a dedicated output folder.
; dentrix-connector.env
DENTRIX_DSN=DentrixRecallDSN
DENTRIX_USER=api_user
DENTRIX_PASS=********
# Optional: if Dentrix program provides a pre-approved SQL or view name
SQL_RECALL_QUERY=SELECT * FROM DENTRIX_RECALL_VIEW
# Fallback file-watch mode
WATCH_DIR=C:\Dentrix\Exports\Recall\
POLL_CRON=0 */2 * * * ; every 2 hours
CRM_WEBHOOK_URL=https://example-crm.com/hooks/dental-recall
IDEMP_DB=C:\Dentrix\connector\state.sqliteKey gotcha: there is no internet-facing API for Dentrix on-prem, so do not attempt to call it from the cloud. Place the connector in-office.
2) Connect via ODBC with short-lived sessions
We keep ODBC connections short-lived to avoid holding locks. The SQL text comes from a configuration value provided by the Dentrix program or a named view the program enables. We do not hardcode table names.
// connector/odbc-client.js
const odbc = require('odbc');
require('dotenv').config({ path: './dentrix-connector.env' });
async function readRecallBatch() {
const cnStr = `DSN=${process.env.DENTRIX_DSN};UID=${process.env.DENTRIX_USER};PWD=${process.env.DENTRIX_PASS}`;
const sql = process.env.SQL_RECALL_QUERY; // provided by Dentrix program config
if (!sql) throw new Error('Missing SQL_RECALL_QUERY');
const cn = await odbc.connect(cnStr);
try {
const rows = await cn.query(sql);
return rows; // normalize downstream without assuming field names here
} finally {
await cn.close();
}
}
module.exports = { readRecallBatch };If program access is pending, skip ODBC and use file-watch mode next.
3) Fallback: watch scheduled report exports
Dentrix can schedule reports and tasks to run automatically. When recall lists are exported to Excel or CSV on a schedule, the connector watches the folder, parses new files, and emits a normalized record with only the fields your CRM needs.
// connector/file-watch.js
const chokidar = require('chokidar');
const XLSX = require('xlsx');
const path = require('path');
function onNewExport(file, emit) {
const wb = XLSX.readFile(file);
const first = wb.SheetNames[0];
const rows = XLSX.utils.sheet_to_json(wb.Sheets[first], { defval: '' });
// Map columns by header text configured in practice's export template
const out = rows.map(r => ({
name: r['Patient Name'],
phone: r['Mobile'],
email: r['Email'],
dueLabel: r['Recall Status'],
officeCode: path.basename(file).slice(0, 4)
}));
emit(out);
}
function startWatcher(dir, emit) {
chokidar.watch(dir, { ignoreInitial: true })
.on('add', f => onNewExport(f, emit));
}
module.exports = { startWatcher };The header names come from the practice's export template. If that template changes, update the mapping.
4) Add an idempotency ledger to prevent duplicates
Every push to the CRM is checked against a local ledger keyed by office, contact, and the reason for contact. We use SQLite for a zero-admin state store.
-- connector/state.sql
CREATE TABLE IF NOT EXISTS sent_keys (
office TEXT NOT NULL,
key TEXT NOT NULL,
sent_at TEXT NOT NULL,
PRIMARY KEY (office, key)
);// connector/ledger.js
const Database = require('better-sqlite3');
const db = new Database(process.env.IDEMP_DB);
db.exec(require('fs').readFileSync('connector/state.sql', 'utf8'));
function wasSent(office, key) {
return !!db.prepare('SELECT 1 FROM sent_keys WHERE office=? AND key=?').get(office, key);
}
function markSent(office, key) {
db.prepare('INSERT OR IGNORE INTO sent_keys (office, key, sent_at) VALUES (?, ?, datetime("now"))').run(office, key);
}
module.exports = { wasSent, markSent };The key can be a stable hash of name, date-of-birth or chart number, and the recall category, respecting your practice's privacy policy.
5) Push to the CRM with clear segments and consent
We post records to the CRM with routing tags for due-soon, due-now, and overdue. The CRM owns messaging cadence, opt-out capture, and phone-type fallbacks.
// connector/push-crm.js
const fetch = require('node-fetch');
const { wasSent, markSent } = require('./ledger');
async function pushBatch(office, records) {
for (const r of records) {
const key = `${r.name}|${r.officeCode}|${r.dueLabel}`;
if (wasSent(office, key)) continue;
const payload = {
name: r.name,
phone: r.phone,
email: r.email,
office: office,
tags: [r.dueLabel, 'dentrix-recall'],
source: 'dentrix-connector'
};
const resp = await fetch(process.env.CRM_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (resp.ok) markSent(office, key);
}
}
module.exports = { pushBatch };If the CRM provides a two-way suppression list, pull that at the start of each run to avoid re-pushing opt-outs or hard bounces.
6) Schedule, monitor, and dry-run before go-live
We run the connector on a cron-like schedule and emit health pings. A dry-run prints which patients would be sent without actually calling the CRM endpoint. Turn dry-run off only after the first narrow live batch confirms routing and consent.
# Windows Task Scheduler: start connector every 2 hours
Program/script: node
Arguments: C:\Dentrix\connector\index.js --run
Start in: C:\Dentrix\connector
# Ensure the task runs whether user is logged in or not, with highest privilegesA daily summary email from the connector helps catch report-template drift and folder-permission changes early.
Where it gets complicated
- Local-only by design: Dentrix on-prem does not expose an internet API. Each office needs its own local connector. Coordinate with the practice's IT so the service runs on a machine that stays on and survives reboots.
- No HL7 and limited transparency: The Dentrix on-prem program does not use HL7 and does not publish a general schema. Access is through curated views or stored procedures, and not every scheduling surface is available to non-commercial participants. Plan for the exact objects your program enrollment enables.
- Report-export variance: If you use the Batch Processor path, report columns can change when staff adjust templates. Keep header-to-field mapping in config and add a unit test that fails when headers drift.
- Consent and contact routing: Mobile vs home phone, SMS consent states, and family-level preferences matter. Mirror the practice's policy in the connector, then let the CRM manage opt-outs and suppressions thereafter.
- Multi-location realities: Each office is a separate Dentrix environment. We installed one service per office, all pointing to the same CRM with an office code tag so sequences and reporting segment cleanly.
- Connection health: Use short-lived ODBC sessions, exponential backoff, and fallbacks to avoid tripping locks. Log query latency and result counts so a thin batch alerts humans before a clinic day begins.
What this actually changes
For a multi-location dental group we supported, recall stopped depending on a person remembering to export a list. The connector runs on a schedule, CRM sequences run without manual sends, and opt-outs are honored automatically across offices. Staff time shifted from list wrangling to booking replies and handling edge cases.
Retention economics justify the work. It is generally far more expensive to acquire a new patient than to keep an existing one. Harvard Business Review reports that acquiring a new customer can cost five to twenty five times more than retaining an existing one. Source: https://hbr.org/2014/10/the-value-of-keeping-the-right-customers
Frequently asked questions
Does Dentrix have an API we can use?
Yes. Dentrix on-prem offers access through the Dentrix API Program, which enables local integrations that authenticate to curated views or stored procedures via a password-protected ODBC connection. There is no public internet HTTP API for on-prem Dentrix, and HL7 is not supported on the on-prem product. Dentrix Ascend is a different product with a REST API and does not apply to on-prem.
Can I use Zapier or Make with Dentrix?
Not directly. Dentrix directs integrations through its API Exchange and approved partners. There is no official Dentrix app in Zapier or Make for on-prem deployments. The practical approach is a small local connector that talks to Dentrix in-office, then relays to your CRM or messaging tools in the cloud.
Is this real-time or batch?
Dentrix on-prem is local-only. We schedule the connector in near-real-time batches, for example every two hours, or watch a folder for scheduled report exports. That keeps messaging timely without overloading the office server. Fully real-time webhooks are not part of the on-prem surface.
What do we need from IT to install this?
A Windows machine that is always on, a program-issued Dentrix API credential with the connection string or DSN, permission to create a local service and a small SQLite file for state, and outbound HTTPS to your CRM webhook. If you use the export path, we need a dedicated folder with write permissions from the Batch Processor.
How long does implementation take?
For a single office, we typically deploy inside one to two weeks including dry-run and the first narrow live batch. Multi-location groups add time because each office needs its own installer and validation. The connector is reusable, so later offices go faster.
What does this cost monthly?
The connector itself is lightweight and runs on existing hardware. Ongoing costs are your CRM and messaging platform, plus any program fees Dentrix charges for API access. There is no separate cloud runtime fee for the on-prem connector.
If you want to replace weekly recall list exports with a durable, compliant automation, this is the exact deployment pattern we ship. See how we approach CRM execution in our related post on GoHighLevel orchestration: /blog/automate-gohighlevel-crm-make. For broader CRM integration work, see our services page. When you are ready, book a 15-minute call and we will map your offices to this pattern.
Want us to build this for you?
15-minute discovery call. No pitch. We tell you what to automate first.
Book a Discovery Call