How it works
- Step 1Export from each lead source — Follow Up Boss → Contacts → Export. Sierra Interactive → Leads → Filter → Export CSV. Zillow Premier Agent → Contacts → Export CSV. Realtor.com → Leads → Export. Facebook Lead Ads → Leads Center → Export CSV. Keep each file separate — Step 1 unions them for you.
- Step 2Fork the workflow into your drafts — Click
Fork to my drafts. You must be signed in (forking requires auth but no paid tier). The fork copies the 8-node graph into a private workflow you own, snapshots it as version 1, and records aforkedevent in the audit log. Drop your multiple lead CSVs onto Step 1 (csv-merger). - Step 3Fix the regex nodes before your first run — Steps 4 and 5 carry
useRegex: true, but the workflow executor does not pass that flag through, so the pattern runs as literal text. Either edit Step 4 to a literal value (e.g. one pass removing-, one removing(, etc.) or run the regex form inside the standalone `csv-find-replace` tool, which DOES honouruseRegex, before feeding the file in. - Step 4Before Step 7 — verify TCPA opt-out preservation — If any source CSV has a
DNC,opt_out,unsubscribed, ortcpa_consentcolumn, **pause before Step 7**. Use `csv-column-filter` to isolate opted-out rows, dedupe them separately, and keep the opt-out version. The deduplicator keeps the first row it sees regardless of opt-out status — it has no strictest-merge logic. - Step 5Pick the target CRM's rename JSON and set Step 7's key — Edit Step 8's
renamesmap for Follow Up Boss, Sierra Interactive, kvCORE, Lofty/Chime, or Top Producer (see cookbook). Because Step 7 dedupes on one column, setEmailas the first key for the cleanest result, then re-run withPhoneas the key on the leftovers if you need both. - Step 6Run, then import to your CRM — Run the workflow — every step processes in your browser. Sierra: Leads → Import. Follow Up Boss: People → Import. kvCORE: Contacts → Import. Cross-check the post-import count against the workflow output, and check the run history (audit trail) if a step errored.
The 8-node chain (what actually runs)
Each blueprint node, its config, and the verified runtime behaviour. Two nodes carry config the executor does not yet honour — flagged below.
| Step | Tool | Blueprint config | Runtime behaviour |
|---|---|---|---|
| 1 | `csv-merger` | {} | Union merge of every dropped file; pads mismatched columns. Honoured as configured. |
| 2 | `csv-cleaner` | trim · removeEmptyRows · repairRows · dedupe:false · smartQuotes · hiddenWhitespace | Trims, drops blanks, repairs ragged rows, normalises quotes/NBSP. Honoured. |
| 3 | `csv-special-char-stripper` | keepLetters · keepDigits · keepSpaces · keepPunctuation:false | Keeps [a-zA-Z0-9 ], strips everything else from ALL cells. Honoured. |
| 4 | `csv-find-replace` | find:[()\s\-\.] · replace:empty · useRegex:true | **useRegex is dropped by the executor** → pattern runs as a literal string, so the bracket class does NOT match. Needs a literal-value fix. |
| 5 | `csv-find-replace` | find:Zillow (...|...) · replace:Zillow · useRegex:true | Same useRegex-dropped caveat. Replace with literal passes per variant. |
| 6 | `csv-case-converter` | caseType:lower · columnIndices:empty | Empty column list = convert every cell to lower-case. Honoured. |
| 7 | `csv-deduplicator` | columns:Email,Phone · strategy:first | **Single-column, case-insensitive, keep-first.** Executor uses only the first listed column and ignores strategy. Composite Email+Phone key is NOT applied. |
| 8 | `csv-header-rename` | renames: per-CRM JSON | Maps each named header to its new label by case-insensitive lookup. Honoured. |
Fork, run, schedule & tier matrix
What the orchestrator actually requires for each action on this workflow, verified against the fork route, tier-precheck, and cron tick.
| Action | Requirement | What happens |
|---|---|---|
| Fork to drafts | Signed in (no paid tier needed) | Builds the graph, creates a **private** workflow you own, snapshots v1, writes a forked audit event tagged with the blueprint slug. |
| Run the workflow | Free tier | All 7 chained tools have no minTier, so the strictest required tier is free. CSV contents process in your browser; run + step rows are recorded for the trace history. |
| Credentials / row data | None stored server-side | These CSV tools take no API credentials. Row data is processed client-side; only the workflow graph and run metadata are persisted (Supabase). |
| Schedule (cron) | Set schedule_cron on your fork | This blueprint ships scheduleCron: null, so a fresh fork is **unscheduled**. Add a cron expression and the Cloudflare cron tick enqueues a source: cron run when it's due. |
| Audit / trace history | Recorded automatically | create, update, publish, rollback, fork, delete, run trigger/cancel/resume each write a workflow_audit_events row (fire-and-forget; enterprise compliance). |
CRM column maps and import limits
Each CRM's auto-mapped column header names. Match them in Step 8 and you skip the manual mapping step.
| CRM | Auto-mapped column headers | File size / row limit | Notable rules |
|---|---|---|---|
| **Follow Up Boss** | first name, last name, email, phone, address, source, tags, notes | **90 MB** file size | **One value per cell** — comma-separated multi-values are silently dropped. Special chars (#$@!) degrade performance (handled at Step 3). |
| **Sierra Interactive** | First Name, Last Name, Email, Secondary Email, Phone, Secondary Phone, Lead Source, Lead Type, Assigned Agent, Registered Date | No documented limit | **Email must be unique** as primary. Import Duplicates does NOT reassign agent ownership. |
| **kvCORE** | First Name, Last Name, Email, Phone, Source, Status, Type, Notes, Address, Tags, Agent | **10,000 rows recommended split** | Auto-matches case-insensitively. |
| **Lofty / Chime** | First Name, Last Name, Email, Phone, Source, Lead Type, Stage, Tags | CSV/XLS/XLSX; **split files over 10,000 rows** | Most flexible auto-mapping. |
| **Top Producer CRM** | First Name, Last Name, Email, Cell Phone, Home Phone, Work Phone, Source, Type, Status | Not publicly documented | Three distinct phone columns — split before import. |
Cookbook
Drop-in renames JSON for Step 8 (`csv-header-rename`) by target CRM, plus the literal-value passes to substitute for the regex Steps 4–5 the executor doesn't run as regex. The header-rename runtime matches keys case-insensitively and renames each matched column to its mapped value.
Follow Up Boss rename JSON (the shipped default)
ExampleFollow Up Boss uses lowercase column names. This is the exact map the blueprint forks with at Step 8.
{
"First Name": "first name",
"Last Name": "last name",
"Email": "email",
"Phone": "phone",
"Source": "source"
}Sierra Interactive rename JSON
ExampleSierra auto-maps these exact title-case field names. Feed your normalised lowercase headers as keys, Sierra's labels as values.
{
"first name": "First Name",
"last name": "Last Name",
"email": "Email",
"phone": "Phone",
"source": "Lead Source",
"type": "Lead Type"
}Top Producer rename JSON (3 phone columns)
ExampleTop Producer expects three distinct phone columns. Split phone first with csv-column-value-splitter, then map. See /tool/csv-column-value-splitter.
{
"first name": "First Name",
"last name": "Last Name",
"email": "Email",
"phone_cell": "Cell Phone",
"phone_home": "Home Phone",
"source": "Source"
}Replace the regex phone pass (Step 4) with literal passes
ExampleBecause the executor ignores useRegex, run csv-find-replace as a chain of literal removals instead of one bracket class. Each pass strips one character.
// Step 4a find: "(" replace: "" useRegex: false
// Step 4b find: ")" replace: "" useRegex: false
// Step 4c find: "-" replace: "" useRegex: false
// Step 4d find: "." replace: "" useRegex: false
// Step 4e find: " " replace: "" useRegex: false
// Result: (555) 555-1234 -> 5555551234Two-pass dedup to approximate an Email+Phone key
ExampleStep 7 dedupes on one column only. Run it twice — once keyed on Email, then re-run the output keyed on Phone — to catch rows that share a phone but differ on email.
// Pass 1 csv-deduplicator columns: "email" // collapses leads that share an email address // Pass 2 csv-deduplicator columns: "phone" // collapses remaining leads that share a phone number // (both passes keep the FIRST occurrence, case-insensitive)
Edge cases and verbatim errors
Step 7 dedupe does not use the Email+Phone composite key
Config not honouredThe blueprint sets columns: "Email,Phone", but the deduplicator runtime reads only the FIRST listed column as the key and ignores strategy. So it dedupes on Email alone, case-insensitively, keeping the first row. Two leads with the same phone but different emails survive as separate rows. Workaround: run `csv-deduplicator` twice — first keyed on email, then re-run the output keyed on phone.
Steps 4 & 5 regex patterns match as literal text
Config not honouredBoth find/replace nodes set useRegex: true, but the workflow executor passes only caseSensitive to the engine — useRegex is dropped, so the engine escapes the pattern and matches it literally. [()\s\-\.] therefore matches the literal string, not a character class, and nothing changes. Fix: replace each regex node with literal-value passes (one char per pass), or pre-process in the standalone `csv-find-replace` tool, which honours useRegex.
TCPA opt-out flag stripped during keep-first dedup
Compliance liabilityIf two rows match on the dedup key and only one carries a Do-Not-Contact flag, Step 7 keeps whichever appears first — it has no strictest-merge logic. Subsequent outreach is a TCPA violation: $500 per call/text, trebled to $1,500 for willful. **Manually verify**: filter opted-out rows with `csv-column-filter` before Step 7 and merge them back after, keeping the opt-out version on conflict.
Special-char stripper removes dots and @ from emails
Order-sensitiveStep 3 keeps only letters, digits, and spaces — so it strips . and @ from EVERY cell, including emails, turning jo@x.com into joxcom. The blueprint runs it before any email-keyed step, which is why dedup keys on lowercased emails that may already be mangled. If you need intact emails for matching, exclude email columns from Step 3 or move name-cleaning to a name-only column subset.
Empty or single source dropped onto Step 1
fail`csv-merger` throws CSV Merge requires at least one file input when nothing is on the node. A single file round-trips fine (union of one), but you get no dedup benefit — the point of the workflow is multiple sources. Drop every export at once.
Forked workflow does not run on a schedule
ExpectedThis blueprint ships scheduleCron: null, and the fork copies that value, so a fresh fork never fires automatically. To automate (e.g. nightly lead merges), set a schedule_cron expression on your draft. The Cloudflare cron tick then enqueues a source: cron run via workflow_run_queue when isDueSince says it's due.
Fork attempted while signed out
401The from-blueprint route returns 401 unauthorized if it can't resolve a session email or access-token subject. Forking needs auth (any tier); running the resulting workflow needs only the free tier since all 7 nodes are free.
Follow Up Boss: file exceeds 90 MB
Hard limitFollow Up Boss caps imports at 90 MB. Split the workflow output by date range or lead source using `csv-row-limiter` or `csv-row-splitter` before upload.
Sierra Import Duplicates does not reassign agent ownership
Sierra-specificSierra's Import Duplicates mode fills blank fields on existing leads but does NOT reassign agent ownership even if your CSV lists a different Assigned Agent. Bulk reassignment requires the Sierra UI or API — the workflow can't force it via headers.
International phone without country code
Ambiguous identifier555 1234567 could be Canadian, Australian, UK, or a US number missing the area code. Step 4 (once fixed to strip separators) reduces it to digits but can't infer the country. Manually inspect rows whose phone length falls outside 7–11 digits before trusting them as a dedup key.
Frequently asked questions
Does Step 7 really dedupe on both email AND phone?
No — that's a config-vs-runtime gap worth knowing. The blueprint sets columns: "Email,Phone", but the deduplicator runtime reads only the FIRST listed column as the key and matches case-insensitively, keeping the first row. It also ignores the strategy field. To approximate a composite key, run `csv-deduplicator` twice: once keyed on email, then re-run the output keyed on phone.
Why don't the regex find/replace steps change anything?
Steps 4 and 5 carry useRegex: true, but the workflow executor only forwards caseSensitive to the find/replace engine. With useRegex dropped, the engine escapes your pattern and matches it literally, so [()\s\-\.] looks for that exact text and finds nothing. Use literal-value passes (one character per pass) inside the workflow, or run the regex form in the standalone `csv-find-replace` tool, which honours useRegex.
What does the fork button actually do?
It calls the from-blueprint route, which builds the 8-node graph, creates a **private** workflow owned by you, snapshots it as version 1, and writes a forked audit event tagged with the blueprint slug. You must be signed in, but no paid tier is required to fork. The forked draft is yours to edit and re-wire on the canvas.
Is this workflow free to run?
Yes. The tier precheck takes the strictest minTier across all nodes, and all seven chained CSV tools have no minTier set, so the required tier is free. Tier gating only matters if you later add a Pro or Pro+Media node to your fork.
Where is my lead data processed — does it go to a server?
The CSV steps run client-side: the executor dynamically imports the CSV processors and works on the file in your browser tab. The orchestrator persists the workflow graph and run/step metadata (for the trace history) to Supabase, but not your row contents. These CSV tools also take no API credentials, so there are no secrets to store.
Can I schedule this to run automatically every night?
Yes, but it isn't scheduled by default. The blueprint ships scheduleCron: null and the fork copies that, so set a schedule_cron expression on your forked draft. The Cloudflare cron tick (/api/orchestrator/cron/tick) walks every workflow with a non-null cron, checks whether each is due since its last_fired_at, and enqueues a source: cron run.
Is there a run history / audit trail?
Yes. The orchestrator writes workflow_audit_events rows for create, update, publish, rollback, fork, delete, and run trigger/cancel/resume. Writes are fire-and-forget so they never slow the user path, and they're intended for enterprise compliance. Per-run step statuses also surface in the run panel as the workflow executes.
How do I dedupe leads exported from Zillow and Realtor.com on the same day?
Step 1 (`csv-merger`) unions the files; Step 5 normalises Zillow Premier Agent / Tech Connect / Flex / Make Me Move to Zillow (use literal passes given the regex caveat); Step 7 then collapses duplicates on the email key, keeping the first occurrence. Re-run Step 7 on phone to catch same-phone, different-email pairs.
Will the workflow strip my TCPA opt-out flags?
It can, if you run it blind. Step 7 keeps the first matched row regardless of opt-out status and has no strictest-merge logic. If your sources carry DNC / opt_out / tcpa_consent columns, isolate opted-out rows with `csv-column-filter` before Step 7 and merge them back keeping the opt-out version. Stripping a flag and then contacting that lead is a TCPA violation ($500–$1,500 per call/text).
Which phone format does my CRM or dialer want?
**E.164 (+15555551234)** for Twilio / RingCentral / Mojo. **10-digit raw (5555551234)** for Follow Up Boss. **Display-formatted ((555) 555-1234)** for Top Producer. The phone-normalisation step (once fixed to strip separators) yields 10-digit raw; add a final literal `csv-find-replace` prepending +1 if you need E.164.
How large a lead list can the workflow handle?
Because processing is in-browser, the practical ceiling is your tab's RAM — comfortably hundreds of thousands of rows on desktop Chrome. Downstream import limits bind first: Follow Up Boss = 90 MB; kvCORE and Lofty/Chime recommend splitting over 10,000 rows. For large outputs, split with `csv-row-splitter` after the run.
How does this differ from the supplier-inventory and CRM-export merge workflows?
All three start with `csv-merger` but tune the post-processing for their domain. This one adds phone normalisation, lead-source canonicalisation, special-char stripping, and TCPA handling for real-estate CRMs. See Merge Supplier Inventory CSVs for SKU-keyed inventory feeds and Merge CRM Export CSVs for Salesforce/HubSpot/Pipedrive contact merges.
Local-first by design
This workflow executes entirely on your jadapps-runner. API keys, database credentials, and OAuth tokens are stored in an AES-GCM-encrypted vault on your device — they are never uploaded to JAD Apps' servers. The server only stores the workflow graph (the recipe), not the secrets.