Skip to main content

 About Waqar Khan

Waqar leads software engineering at DataTools Pro, overseeing the platform, integrations, and APIs. With over 12 years of consulting experience and 100+ projects completed prior to joining full-time in 2023, he specializes in Salesforce, Snowflake, workflow automation, pipelines, and API integrations, turning complex business and technical requirements into scalable solutions. He currently leads R&D across agentic automation, headless Salesforce, and specialized migration services.

Snowflake Performance Optimization: How We Reduced a Data Import from 2 Minutes to 8 Seconds

Snowflake performance optimization is often approached by looking at warehouse size first. A slow workload appears, the warehouse is resized, performance improves, and the problem appears solved.

But additional compute can hide an inefficient workload rather than fix it.

In this case study, we used Snowflake Query History, Query Profile, application-level instrumentation, and stored-procedure profiling to identify where time was actually being spent in a data-processing application.

The first optimization reduced a data import from approximately two minutes to eight seconds without increasing warehouse size. A second investigation then isolated a Cortex Search operation responsible for roughly 75% of an account-matching procedure’s execution time.

The important part wasn’t any single optimization technique. It was the process:

Measure → isolate → understand → optimize → verify.


The Test Workload

The application processes customer datasets inside Snowflake and includes ingestion, data cleaning, and account-matching workflows.

For performance testing, we kept the workload consistent:

Input rows:       123,777
Input columns:    5
Warehouse size:   X-Small

Using the same dataset, warehouse, and application settings was important because it gave us a reliable baseline for comparing changes.


Optimization 1: Fixing the Data Import

The original import took approximately:

~120 seconds

Rather than resizing the warehouse, we started with Snowflake Query History.

The baseline revealed:

OperationQueriesTotal Time
INSERT12493.56 sec
SELECT399.88 sec
SHOW52.45 sec
CREATE TABLE11.03 sec
DESCRIBE10.41 sec
USE20.31 sec

The dominant pattern was immediately visible:

124 INSERT statements were consuming approximately 94 seconds.

The application was splitting the DataFrame into batches of roughly 1,000 rows and repeatedly issuing SQL INSERT operations.

Conceptually:

123,777 rows
      ↓
~1,000-row batches
      ↓
INSERT
INSERT
INSERT
...
~124 times

This was primarily an ingestion architecture problem, not evidence that the X-Small warehouse lacked sufficient compute.


Replacing Repeated INSERTs with Snowpark Bulk Loading

The row-batch INSERT implementation was replaced with Snowpark’s write_pandas() bulk-loading path.

The revised flow became:

Pandas DataFrame
      ↓
Snowpark write_pandas()
      ↓
App-owned staging table
      ↓
CTAS into destination
      ↓
Drop staging table

A simplified version of the loading operation:

session.write_pandas(
    df,
    table_name=stage_table,
    database=current_database,
    schema="CORE",
    auto_create_table=True,
    overwrite=True,
    quote_identifiers=True
)

The final destination table could then be created server-side:

CREATE OR REPLACE TABLE destination_table AS
SELECT *
FROM staging_table;

Using an application-owned staging area was also important in the Native App architecture because it avoided requiring additional stage-creation privileges in the consumer’s destination schema.


Import Performance After Optimization

Application-level instrumentation showed:

StageExecution Time
CSV read0.175 sec
DataFrame preparation0.069 sec
Bulk load5.827 sec
Stage check0.181 sec
Table creation1.308 sec
Cleanup0.282 sec
Row verification0.194 sec
Total8.038 sec

The change was substantial:

Before
~120 seconds
124 INSERT operations

        ↓

After
~8 seconds
Bulk ingestion

That is approximately a 93% reduction in application processing time for the tested import.

More importantly, the improvement was achieved without increasing warehouse size.


Optimization Lesson: Architecture Before Warehouse Size

A warehouse resize might have improved the original workload somewhat, but it would not have removed the fundamental inefficiency of issuing more than 100 separate INSERT operations.

This leads to a useful optimization principle:

Before adding compute, determine whether the application is asking Snowflake to perform unnecessary work.

Warehouse sizing remains an important optimization tool, but it should be evaluated after understanding the execution pattern.


Profiling the Clean-and-Save Workflow

After improving ingestion, we moved to the next workflow rather than assuming it was also slow.

Instrumentation produced:

StageExecution Time
Original count0.174 sec
Raw column processing0.714 sec
Final cleaning1.405 sec
Preview fetch0.257 sec
Final row count0.101 sec
Total2.682 sec

At approximately 2.7 seconds, this was not a meaningful optimization target compared with other application operations.

This is an important part of performance engineering: knowing what not to optimize.


Optimization 2: Profiling Account Match Back

The next workflow was significantly more expensive.

Account Match Back performs candidate discovery and scoring against an account search index.

For the same workload:

Input rows:       123,777
Candidate rows:   618,797
Final rows:       106,041

Application-level profiling showed:

StageExecution Time
Matching procedure54.207 sec
Final output to Pandas1.575 sec
Relationship fetch0.166 sec
Snapshot1.881 sec
Run-log insert0.995 sec
Python processing2.075 sec
Results page7.219 sec
Total61.463 sec

This immediately eliminated one possible assumption.

With more than 100,000 final rows, moving results into Pandas might appear suspicious. In practice, to_pandas() accounted for only approximately 1.6 seconds.

The real bottleneck was inside the matching procedure.


Profiling Inside the Stored Procedure

We instrumented the matching procedure itself.

The result:

Procedure StageExecution Time
Input setup1.633 sec
Column mapping6.645 sec
Normalization2.068 sec
Company search42.927 sec
DBA search0 sec
Scoring2.369 sec
Cleanup1.210 sec
Count0.572 sec
Total57.424 sec

The bottleneck was now isolated:

SEARCH_COMPANY_SECONDS = 42.927

Company search represented approximately 75% of the matching procedure’s execution time.

At this point, optimizing scoring, Python processing, or result serialization would have had relatively little impact.


Inside the Cortex Search Operation

The expensive stage uses Snowflake Cortex Search through a lateral batch-search pattern:

CREATE OR REPLACE TABLE core._ACCT_MB_CAND_C AS
SELECT
    p._MB_ROW_ID AS input_row_id,
    p.ic_q,
    s."RECORD_ID" AS crm_record_id,
    s."COMPANY" AS crm_company,
    s."COMPANY_CLEAN" AS crm_company_clean,
    s."METADATA$RANK"::INTEGER AS search_rank
FROM core._ACCT_MB_PREP p,
LATERAL CORTEX_SEARCH_BATCH(
    service_name => '<db>.CORE.ACCT_SEARCH_SVC',
    query        => p.ic_q,
    limit        => <TOP_K>
) s;

The workload operates at input-row grain. For each prepared row, its normalized company-name value is supplied to CORTEX_SEARCH_BATCH, and the resulting candidates are materialized into an intermediate candidate table.

The test used:

Input rows:            123,777
TOP_K:                 5
Generated candidates:  618,797

That is effectively five candidates per input row.


Query Profile Provided Another Important Signal

The corresponding expensive operation showed a profile dominated by:

Remote disk I/O:   87.3%
Synchronization:   10.7%
Processing:         0.8%

The candidate-generation operation also wrote approximately 618,797 rows.

That profile is materially different from a workload dominated by warehouse CPU processing.

Simply scaling the warehouse therefore wasn’t the first optimization we wanted to test.

Instead, we investigated how the search workload itself was constructed.


Identifying Redundant Cortex Search Work

The investigation revealed an important property of the current implementation.

The prepared input remains at original input-row grain.

If multiple rows normalize to the same search value, for example:

Row 1 → MICROSOFT
Row 2 → APPLE
Row 3 → MICROSOFT
Row 4 → GOOGLE
Row 5 → MICROSOFT

the same normalized query can participate in the Cortex Search operation multiple times.

The implementation currently performs no deduplication of ic_q before candidate search.

That creates a potential optimization opportunity:

Current

123,777 input rows
        ↓
Normalize
        ↓
Cortex Search at input-row grain
        ↓
618,797 candidate rows

versus:

Potential architecture

123,777 input rows
        ↓
Normalize
        ↓
Distinct search keys
        ↓
Cortex Search
        ↓
Candidate set per distinct key
        ↓
Join candidates back to input-row grain
        ↓
Existing scoring/ranking

The objective is not to reduce TOP_K or alter matching thresholds. It is to determine whether identical search requests can be evaluated once and safely reused across matching input rows.


Measure the Opportunity Before Implementing It

We deliberately did not implement deduplication immediately.

First, the potential reduction needs to be quantified.

A representative analysis is:

WITH prepped AS (
    SELECT
        _MB_ROW_ID,
        CLEAN_COMPANY_NAME(company_name) AS ic_q
    FROM account_matchback_input
)

SELECT
    COUNT(*) AS input_rows,
    COUNT(DISTINCT ic_q) AS distinct_search_queries,
    COUNT(*) - COUNT(DISTINCT ic_q) AS potentially_redundant_searches
FROM prepped;

The critical comparison is:

Total input rows
vs.
Distinct normalized search keys

For example:

123,777 input rows
120,000 distinct search keys

would suggest limited benefit.

Whereas:

123,777 input rows
50,000 distinct search keys

would indicate a substantially larger opportunity to reduce Cortex Search probes.

The actual performance improvement should still be benchmarked rather than inferred directly from the percentage reduction because search-service batching and execution behavior may not scale linearly.


Why We Didn’t Reduce TOP_K

Another obvious optimization would be:

TOP_K = 5
      ↓
TOP_K = 3

That would reduce candidate volume.

But it also changes the semantics of the matching process by reducing candidate recall.

Performance tuning shouldn’t silently change business behavior.

The same caution applies to narrowing the search index with additional filtering. The current search service contains both company and DBA representations, and changing search scope could affect which candidates are discoverable.

Performance-only changes should be separated from changes to matching strategy.


The Optimization Methodology

The most valuable result from this work was a repeatable methodology.

Establish baseline
        ↓
Inspect Query History
        ↓
Identify expensive operations
        ↓
Inspect Query Profile
        ↓
Add application-level timing
        ↓
Instrument expensive procedures
        ↓
Isolate the dominant stage
        ↓
Understand its execution architecture
        ↓
Quantify the optimization opportunity
        ↓
Change one thing
        ↓
Re-run the same workload
        ↓
Compare before and after

This approach prevented several premature optimizations.

We didn’t resize the warehouse simply because ingestion was slow.

We didn’t optimize Clean + Save because it was already completing in approximately 2.7 seconds.

We didn’t optimize to_pandas() based on the assumption that transferring 100,000+ rows must be expensive.

And we haven’t yet changed Cortex Search simply because duplicate search keys appear possible.

Each decision follows measurement.


Results So Far

The ingestion optimization already produced a clear result:

MetricBeforeAfter
Input rows123,777123,777
WarehouseX-SmallX-Small
Import processing~120 sec~8 sec
INSERT pattern~124 INSERTsBulk load
Approx. improvement—~93%

The Account Match Back investigation has isolated the next opportunity:

Total workflow
61.46 sec
   ↓
Matching procedure
~54–57 sec
   ↓
Company search
42.93 sec
   ↓
123,777 search probes
   ↓
618,797 candidate rows

The next benchmark will determine how much of that search workload can be eliminated through search-key deduplication without affecting candidate quality.


Conclusion

Snowflake optimization is not synonymous with warehouse resizing.

Warehouse size matters, but so do ingestion architecture, query patterns, intermediate materialization, external/service-bound operations, application behavior, and unnecessary repetition.

In this case, changing the ingestion architecture reduced processing from approximately:

120 seconds → 8 seconds

on the same X-Small warehouse.

For the second workload, progressive instrumentation narrowed a roughly 61-second workflow down to a single Cortex Search stage consuming approximately 43 seconds.

That is the value of measurement-driven optimization.

Don’t start with:

How much more compute does this workload need?

Start with:

What work is Snowflake actually spending time doing, and does all of that work need to happen?

Then optimize the dominant operation, benchmark it under the same conditions, and let the measurements determine the next step.

Maximizing Your WordPress Funnel with PostHog: Server-Side and Client-Side Tracking Done Right

Hero banner with the headline “Maximizing Your WordPress Funnel with PostHog: Server-Side and Client-Side Tracking Done Right,” showing a laptop screen with a funnel chart and data panels, and labels for Client-Side (posthog-js) and Server-Side (posthog-node) along with WordPress and PostHog logos.

If you’re running lead generation or e-commerce funnels on WordPress, the quality of your analytics is only as good as the quality of your event data. Most sites bolt on a JavaScript tracking snippet, call it done, and then wonder why their conversion numbers don’t match what Sales or Stripe is reporting. PostHog gives you the tools to fix that – but only if you understand the difference between client-side and server-side capture, and why the two need to work together instead of one replacing the other.

This post walks through how to set up a WordPress + PostHog funnel that actually holds up: what to capture in the browser, what to capture on the server, why your web-to-lead form submissions specifically belong on the server side, and how to stitch both halves into one clean user journey.

Client-Side Capture: What It’s Good For

Client-side tracking (the posthog-js snippet, loaded via a plugin like WP PostHog Integration, a custom script in your theme, or Google Tag Manager) is where you get:

  • Autocapture – clicks, form field interactions, page views, and rage-clicks with zero manual instrumentation
  • Session replay – watching real visitors move through your funnel, which is invaluable for finding friction points in a checkout or lead form
  • Feature flags and A/B tests rendered before the page paints
  • Rich browser context – UTM parameters, referrer, device, screen size, scroll depth

This is the layer you want for understanding behavior: where people hesitate, where they abandon, what they interact with before converting. It’s fast to set up and gives you full-funnel visibility with minimal engineering effort.

The catch: client-side events are only as reliable as the browser they run in. Ad blockers, privacy extensions, Safari’s Intelligent Tracking Prevention, and users who simply navigate away before the request fires will all silently drop events. For behavioral analytics, that noise is tolerable. For anything tied to revenue or lead attribution, it isn’t.

Server-Side Capture: What It’s Good For

Server-side tracking uses the posthog-node SDK (or a direct call to PostHog’s Capture API) from your WordPress backend – a custom plugin, a form handler, or a webhook receiver. Server-side events are:

  • Not spoofable or blockable – there’s no browser in the loop to drop the request
  • Authoritative for business-critical actions – payments, subscription changes, and lead form submissions that trigger downstream processes (CRM entry, email sequences, sales notifications)
  • Enrichable with backend data – deal value, lead score, CRM fields, plan tier – data that never touches the browser at all

The tradeoff is that server-side capture on its own tells you that something happened, not the story of how the visitor got there. You lose the session replay, the click trail, the “how did they arrive at this decision” context that client-side gives you.

Which is exactly why you need both.

Why Web-to-Lead Capture Should Be Handled Server-Side

Lead form submissions are the one place I’d push back hardest against a client-side-only setup. Here’s why:

1. Ad blockers and privacy tools disproportionately affect exactly the events you care about most. A form submission fired only via a client-side posthog.capture() call can be blocked before it ever leaves the browser – meaning your most valuable conversion event is the one most likely to go missing from your data.

2. Timing is unreliable. If a visitor submits a form and the page immediately redirects or the tab closes, a client-side event can get cut off mid-flight. A server-side capture, fired the moment your form handler processes the submission, doesn’t have that race condition.

3. It should be the single source of truth for revenue and pipeline reporting. If your form handler is what’s actually creating the lead record (in your CRM, in a spreadsheet, wherever), that’s also the moment that should log the PostHog event – with a distinct_id and properties pulled straight from the validated submission, not from whatever the browser happened to report.

4. It lets you attach data the browser never sees. Lead score, form validation results, spam-filter status, which sales rep gets assigned – all backend-only information you can attach directly to the event at capture time.

The practical pattern: your WordPress form handler (Gravity Forms, WPForms, or a custom wp_ajax hook) fires the posthog-node capture call server-side, right where it already handles the submission and any CRM push. The client-side layer still tracks that the form was viewed, started, and abandoned – but the final “lead captured” event is server-side and authoritative

Coupling Client-Side and Server-Side Tracking

The part that trips most people up isn’t setting up each layer individually – it’s making sure they describe the same person in PostHog instead of creating two disconnected identities.

Use one distinct_id across both layers. When a visitor lands anonymously, posthog-js assigns them an anonymous distinct_id and stores it in a cookie. When your server needs to fire an event for that same visitor (say, on form submission), pull that same distinct_id from the PostHog cookie (ph_<project_key>_posthog) server-side and pass it into your posthog-node capture call. This is what stitches the pre-submission browsing session to the post-submission lead record into a single person timeline.

Call identify() once you know who they are. The moment a lead submits real contact info, call posthog.identify() – client-side, server-side, or both – with a stable ID (email, CRM record ID, or user ID). This merges the anonymous browsing history with the now-known person, so you can see the full journey: which landing page, which blog post, which ad, all the way through to the lead record.

Don’t double-fire the same event from both layers. Pick one layer as the source of truth per event type. Page views, clicks, and scroll depth: client-side. Form submission, purchase, subscription change: server-side. If both layers fire “form_submitted,” you’ll double-count conversions and muddy your funnel metrics.

Pass through UTM and session context. If your server-side event needs marketing attribution data (UTM source, campaign, referrer), capture that client-side at page load, store it in a hidden form field or session value, and forward it to the server-side event’s properties when the form is submitted. That way your revenue-attributing event still carries full marketing context.

A Quick Note on GDPR and CCPA

Both client-side and server-side tracking involve processing personal data, so your privacy disclosures need to cover both. Under GDPR, if you have EEA visitors, you generally need opt-in consent before non-essential tracking scripts run, and that consent choice should also govern whether server-side events tied to that visitor fire. Under CCPA, prior consent isn’t required, but if the data you collect is sold or shared, you need a clear opt-out mechanism such as a “Do Not Sell or Share My Personal Information” link. In both cases, your privacy policy should plainly disclose what’s captured (client and server), why, and how visitors can opt out or request deletion – and a consent-management tool (Cookiebot, OneTrust, etc.) wired to suppress both layers, not just the client-side snippet, is the safest way to enforce it. This isn’t legal advice – check with counsel for your specific situation.

Putting It Together

A funnel built this way looks like:

  1. Visitor lands on a page → client-side autocapture logs the page view and UTM context
  2. Visitor scrolls, clicks, starts the form → client-side captures engagement and session replay
  3. Visitor submits the form → your WordPress form handler validates it, creates the lead, and fires a server-side posthog-node event with the same distinct_id, enriched with backend-only lead data
  4. identify() merges the anonymous session into the now-known lead
  5. You get a single, reliable timeline: acquisition channel → on-site behavior → verified conversion – without ad blockers eating your most important number

That combination is what makes PostHog worth the setup effort over a plugin that just drops a tracking pixel and calls it analytics.

Snowflake CRM Record Matching & Lead Matchback

Monitor on a wooden desk shows a DataTools Pro CRM list overview titled 'What’s in your list' with a sidebar navigation.

Your new marketing list has tens of thousands of leads. How many of them are already in your CRM? We build a Snowflake native CRM Record Matching tool to solve accuracy, speed, and fidelity.

If your answer involves exporting CSVs, running VLOOKUPs in Excel, and praying nothing breaks – you’re not alone. And you’re leaving money on the table.

We built our DataTools Pro Snowflake app to remove painfully slow CRM record matching and identity reconciliation workflows forever. It’s a Snowflake native app that cleans, deduplicates, and matches your prospect lists against your CRM data, like Salesforce, without data ever leaving Snowflake.

Here’s the story of why we built it, what it solves, and how it works.

The Problem Nobody Wants to Talk About

Every B2B sales and marketing team runs into the same wall:

You buy a list. You run a campaign. You get leads. Then someone asks: “How many of these are already in Salesforce?”

What follows is a painful, error-prone, multi-hour ritual:

  1. Export your list to CSV
  2. Export Leads from Salesforce. Export Contacts from Salesforce. Export Accounts from Salesforce.
  3. Open Excel. Start matching. First by email. Then by name. Then by company.
  4. Manually tag each row: “Existing Lead,” “Existing Contact,” “Net New.”
  5. Add campaign columns. Add lead source. Add attribution tags.
  6. Pray you didn’t accidentally assign someone else’s lead.
  7. Import back into Salesforce via Data Loader.
  8. Repeat next week.

This process is broken in at least five ways:

  • It’s slow. A 50K-row list can eat an entire day.
  • It’s inaccurate. “John Smith” at “Acme Inc” and “Jon Smith” at “Acme Incorporated” are the same person – but VLOOKUP doesn’t know that.
  • It’s insecure. You’re exporting CRM data to laptops, emailing spreadsheets, storing PII in shared drives.
  • It’s not repeatable. Every analyst does it differently. No audit trail. No consistency.
  • It doesn’t scale. What works for 500 rows collapses at 50,000.

The real cost isn’t the analyst’s time. It’s the revenue you lose when net-new prospects get ignored because they were falsely tagged as “already in CRM” – or when existing customers get cold-called because the match was missed.

What we built

DataTools Pro is a Snowflake Native Application that handles the entire pipeline:

Upload → Clean → Deduplicate → Match → Enrich → Export

All inside Snowflake. No data extraction. No external tools. No Excel.

It matches your prospect lists against Salesforce Leads, Contacts, and Accounts using AI-powered fuzzy matching — then gives you campaign-ready export files you can drop straight into Salesforce.

Feature 1: Data Cleaning That Actually Works

Before you can match anything, you need clean data. And “clean” in CRM data means handling problems like:

Raw InputProblemCleaned Output
john.smith@acme.com, j.smith@personal.comMultiple emails in one cellSplit into 2 separate records
(555) 123-4567Inconsistent phone format5551234567
John smithExtra whitespace, mixed caseJOHN SMITH
5453Truncated zip code05453
Ryan O'BrienSpecial charactersHandled gracefully

DataTools Pro normalizes everything automatically:

  • Smart column detection — AI-powered mapping that reads your column headers and sample data to figure out which field is which. No manual mapping needed for standard fields.
  • Email normalization — splits multi-value email cells into separate records so no match is missed
  • Phone standardization — strips formatting so (555) 123-4567 matches 555.123.4567
  • Deduplication — removes exact duplicates on the fields you choose before matching begins
  • Encoding detection — handles UTF-8, Latin-1, CP1252, and other encodings without you ever noticing

Import Complete

Feature 2: Account Matchback – Match Companies, Not Just People

Sometimes you don’t have individual contacts. You have a list of company names and you need to know which ones are already Salesforce Accounts.

Company name matching is notoriously hard:

Your ListSalesforce AccountSame company?
Acme IncAcme, IncorporatedYes
JP Morgan ChaseJPMorgan Chase & Co.Yes
Smith & Sons LLCSmith and SonsYes
ABC Corp (DBA: Alpha Business)Alpha Business ConsultingMaybe

DataTools Pro handles this with a lexical-first matching strategy:

  1. Company name cleaning – strips legal suffixes (LLC, Inc, Corp, Ltd), removes parenthetical DBA text, normalizes punctuation
  2. AI-powered candidate search – Snowflake Cortex finds semantically similar company names, not just exact matches
  3. JaroWinkler scoring – precise string similarity that rewards matching prefixes (ideal for company names where “Acme” vs “Acme Inc” should score high)
  4. DBA / Legal name support – matches against both primary name and “Doing Business As” names
  5. State-aware matching – if both records have state data and the states differ, it’s flagged as a different business (prevents matching “Smith Plumbing” in Texas with “Smith Plumbing” in Oregon)

Tunable Thresholds

Every business has different tolerance for false positives vs. false negatives. The admin page lets you tune the matching thresholds:

  • High confidence threshold (default: 93) – above this JaroWinkler score, it’s confidently “In CRM”
  • Review threshold (default: 86) – above this, it needs human review
  • Text match boost – if Cortex AI confidence is also high, a “Review” can be promoted to “In CRM”

Feature 3: Person Matchback – Find People in Your CRM

This is the core. You have a list of people. You need to know: who’s already a Lead or Contact in Salesforce, and who’s truly net-new?

How It Works

  1. One-time setup: An admin points the app at your Salesforce Lead table, Contact table, and Account table in Snowflake. The app builds a search index using Snowflake Cortex Search Service.
  2. Run a match: Upload your list (or point to an existing Snowflake table), click “Find Matches.” The app does the rest.
  3. Review results: Every row gets a confidence band:
StatusWhat It Means
AUTO_MATCHHigh confidence. Email match + name match, or phone match + name match. Safe to process automatically.
REVIEWProbable match but needs human eyes. Email matched but name didn’t, or fuzzy name match without a hard identifier.
NO_MATCHNot found in CRM. This is a net-new prospect.

The Matching Intelligence

This isn’t simple email lookup. The matching engine combines multiple signals:

  • Email match – exact match, case-insensitive
  • Phone match – normalized digits, requires 7+ digits to avoid false positives
  • Domain match – extracts domain from email, matches against CRM email domains
  • Name similarity – Levenshtein distance normalized by name length. “Jon” matches “John” at 75% similarity. “Jonathan” matches “Jon” at lower confidence.
  • Company similarity – same fuzzy logic applied to company names

These signals are layered. An email match with a strong name match is AUTO_MATCH. An email match with a completely different name is REVIEW (could be a shared inbox or forwarded email). A name match alone with moderate company similarity is REVIEW.

The result: fewer false positives, fewer missed matches, and a clear audit trail of why each decision was made.

Feature 4: Customer & Opportunity Context

Knowing a company is “in Salesforce” is step one. The real question is: are they already a customer? Do they have open opportunities?

DataTools Pro integrates with your Opportunity data to answer both:

  • Customer flag: Is this account marked as a Customer (by account type field or by having a Won opportunity)?
  • Open opportunities: Does this account have deals in the pipeline right now?
  • Per-account drill-down: Click into any matched account to see all its opportunities — stage, amount, close date, won/lost status.

This turns a simple “already in CRM” check into actionable intelligence. Your sales team instantly knows:

  • “Don’t cold-call these 200 companies — they’re current customers”
  • “These 50 companies have open deals — coordinate with the account owner before marketing to them”
  • “These 300 companies are truly net-new — prioritize outreach”

Feature 5: Append CRM Fields – Enrich Without Rebuilding

After matching, you often need additional CRM data on your results: Industry, Annual Revenue, Lead Source, Owner Name, Phone, Website.

The traditional approach would be to add these fields to the search index. That means:

  • Rebuilding the index (time + compute cost)
  • Heavier index = higher ongoing Cortex costs
  • Need to rebuild every time you want a different set of fields

Our approach: enrich at results time, not index time.

When you click “Append CRM Account Fields,” the app performs a live LEFT JOIN against your source tables using the matched record ID. You pick exactly the columns you want, and they’re added to your results instantly. No index rebuild. No extra Cortex cost.

Owner Name resolution is built in. Select OwnerId from the field list, point the app at your Salesforce User table, and it resolves the raw Salesforce ID to a human-readable name via a second LEFT JOIN.

For Person Matchback, the same feature works across both Lead and Contact tables — the app automatically routes each row’s JOIN to the correct source table based on whether the match was a Lead or Contact.


Feature 6: Constant Variables – No More Manual Column Pasting

Here’s a workflow that every marketing ops person knows too well:

  1. Download matchback results as CSV
  2. Open in Excel
  3. Add a column: “Campaign Name” → paste “Q3 2026 ABM Outreach” in every row
  4. Add another column: “Lead Source” → paste “Purchased List” in every row
  5. Add another: “Import Date” → paste today’s date in every row
  6. Save. Upload to Salesforce.

DataTools Pro replaces this with a point-and-click interface. An admin defines a catalog of constant variables (Campaign Name, Lead Source, Import Batch ID, etc.), and end users fill in the values directly on the results page. One click, and every row gets the columns attached.

No Excel. No copy-paste errors. No forgotten columns.

Feature 7: Campaign-Ready Exports

The final output isn’t just a spreadsheet. DataTools Pro generates Salesforce-ready import files:

For Existing Matches (Campaign Members):

A CSV with CampaignId, ContactId, LeadId, and Status — ready to drop into Salesforce Data Loader and attach to a Campaign. The app handles the Lead vs. Contact routing automatically.

For Net-New Prospects (New Leads):

A CSV with FirstName, LastName, Company, Email, Phone, address fields, and LeadSource — ready for the Salesforce Lead Import Wizard with campaign assignment.

You enter your Campaign ID (or paste the full Salesforce Lightning URL), choose a Campaign Member Status, and download both files. Done.

Feature 8: Run History & Audit Trail

Every matchback run is logged and snapshotted. You can:

  • Review past runs – see when each analysis was run, how many matches were found, how many were net-new
  • Re-open historical results – load any past run’s full results, including appended CRM fields and constant variables
  • Download historical exports – regenerate CSVs from any past run
  • Compare over time – track how your match rates change across campaigns

Each snapshot is immutable. What you saw on Tuesday is exactly what you’ll see when you re-open that run on Friday – even if the underlying CRM data has changed.

Why Snowflake Native?

We could have built this as a standalone SaaS app. Here’s why we didn’t:

Your data never leaves Snowflake

No extraction. No API calls to external services. No data sitting on vendor servers. Your Salesforce data stays in your Snowflake account, and the app runs as a first-party application inside your environment.

You control the compute

The app uses your warehouse. You decide the size. You see the cost. There’s no hidden compute bill from a vendor running queries on your behalf.

Zero infrastructure to manage

No servers. No containers. No Kubernetes. Install the app from the Snowflake Marketplace, grant it access to your tables, and you’re running.

It scales with your data

50 rows or 5 million rows – the same app, the same workflow. Snowflake handles the compute scaling.

Security and governance built in

Snowflake’s role-based access control, network policies, and audit logging all apply. The app can only see what you grant it access to.


The Architecture (For the Technical Folks)

Key technical decisions:

  • Cortex Search Service for candidate retrieval – semantic search that finds “Acme Inc” when you search for “Acme Incorporated”
  • Results-time JOIN for field enrichment – no index bloat, no rebuild cost
  • JaroWinkler + Levenshtein for string similarity – the right algorithms for name and company matching
  • Case-insensitive column resolution via INFORMATION_SCHEMA.COLUMNS – works regardless of how your Salesforce sync tool cases column names
  • Immutable run snapshots – point-in-time results that don’t change when CRM data changes

Who Is This For?

Marketing Operations teams who:

  • Run ABM campaigns and need to segment lists into “existing” vs. “net-new”
  • Import purchased lists and need to check against CRM before creating duplicates
  • Attach campaign attribution to matched records

Sales Operations teams who:

  • Need to identify which target accounts are already in the pipeline
  • Want to enrich prospect lists with CRM data (owner, industry, revenue) before routing
  • Need to flag current customers before outbound campaigns touch them

Revenue Operations teams who:

  • Need a repeatable, auditable process for list matching
  • Want to eliminate the spreadsheet-based matching workflow
  • Need to track match rates over time across campaigns

Data Teams who:

  • Want to keep PII inside Snowflake’s security boundary
  • Need a self-service tool that business users can run without SQL knowledge
  • Want to reduce the number of “can you match this list for me?” requests

What Our Users See After Switching

Before (Manual Process)After (DataTools Pro)
4-6 hours per list matchUnder 10 minutes
VLOOKUP = exact match onlyFuzzy matching catches “Jon” = “John”
No audit trailEvery run logged and snapshotted
Data exported to laptopsData stays in Snowflake
Manual campaign column entry in ExcelPoint-and-click constant variables
Inconsistent process across analystsSame tool, same logic, every time
No opportunity contextCustomer flags + pipeline visibility
One-off work productReusable, repeatable, historical

Next Generation Solution as a Service

We have learned that every org has their own unique marketing process, data challenges, and matchback needs. That is why we offer our DataTools matching solution as a bundled technology-services offering. When we deploy, I lead solution engineering and our customers benefit of having the last 10% of their data machback needs built. If you are interested, feel free to contact the DataTools Pro team and setup a meeting

Snowflake CoWork: Building Your First AI Business Agent Prototype

Infographic of Snowflake CoWork: two people with laptops around a central circle showing the Snowflake logo and 'CoWork', with icons for Edit, Chat, and Share above it.

Artificial Intelligence advancements are moving very fast for analytics builders and information consumers. Traditionally, business users depended on analysts and data teams to answer questions buried within complex databases. With Snowflake CoWork, organizations have a new path to create AI-powered agents that understand natural language and provide answers directly from enterprise data. Layering LLMs on top of data directly presents a host of challenges that Snowflake has approached throughtfully.

In this article, I’ll walk through my experience creating my first Snowflake CoWork agent using Snowflake CoWork and demonstrate how quickly you can build an AI assistant on top of your Snowflake data.

What is Snowflake CoWork?

Snowflake CoWork is an AI workspace that allows users to:

  • Chat with enterprise data using natural language
  • Connect structured and unstructured data sources
  • Build specialized AI agents
  • Generate insights without writing SQL
  • Enable business users to self-serve analytics

Instead of asking a data analyst:

“Can you tell me why sales increased in July?”

You can simply ask your AI agent directly.

Why Snowflake CoWork Matters

Many organizations struggle with:

  • Data scattered across multiple systems
  • Long wait times for analytics requests
  • Non-technical users unable to query data
  • Knowledge trapped inside reports and dashboards

Snowflake CoWork bridges this gap by allowing AI agents to understand business context and retrieve answers from trusted enterprise data sources.

Setting Up the Environment

To get started, I followed Snowflake’s official Snowflake Intelligence quickstart guide.

The setup script automatically creates:

  • Database and schemas
  • Sample sales data
  • Marketing campaign data
  • Product catalog information
  • Social media metrics
  • Semantic models for business understanding

The result is a fully functional AI-ready environment.

Creating My First Agent

After completing the setup, I created a simple agent named Sales_AI. This agent was connected to a semantic model called:

SALES_AND_MARKETING_DATA

This semantic model allows the AI agent understand business concepts such as:

  • Products
  • Revenue
  • Units Sold
  • Marketing Campaigns
  • Social Media Activity

This blended data pulls data from different sources with different grains. This is where data stewardship, subject matter expertise matter the most.

Instead of thinking in tables and columns, the agent understands business terminology. In this specific case, I had known data, a clear data dictionary, and established semantics from years of building similar reports. We have learned that AI produced semantics can be generic.

The Snowflake CoWork Interface

Here’s the agent running inside Snowflake CoWork:

In my environment, I created the Sales_AI agent and started interacting with it using natural language questions. The interface provides:

  • Chat-based interaction
  • Agent details panel
  • Connected data objects
  • Context review
  • Retrieval transparency

One feature I particularly liked is that Snowflake shows what context the agent reviewed before generating an answer. This execution plan provides guidance and explainaiblity and signals for improvement. I am working on processes that help me automate this process.

Asking Business Questions

Once the agent was created, I started asking business questions.

Example:

Why did sales of Fitness Wear grow so much in July?

The agent automatically:

  1. Interprets the question
  2. Identifies relevant datasets
  3. Generates the required SQL
  4. Retrieves the data
  5. Produces a business-friendly explanation

This removes new analysts to figure out where and how to answer basic questions. For self service, getting these agents to production requires a much deeper level of validation, refinement team readiness.

Understanding Agent Context

During testing, I asked:

What issues are reported with jackets recently in customer support tickets?

The response revealed something important. The agent correctly explained that its current semantic model only included:

  • Marketing campaign metrics
  • Product catalog
  • Sales transactions
  • Social media activity

It did not have access to customer support ticket data. This demonstrates a key strength of Snowflake CoWork. The AI experience will not simply hallucinate answers. Instead, it understands its available data sources and explains its limitations when the requested information is unavailable.

Structured Data vs Unstructured Data

Snowflake CoWork becomes even more powerful when combining:

Structured Data

Examples:

  • Salesforce opportunities
  • Revenue
  • Campaign performance
  • Product sales

Unstructured Data

Examples:

  • Support tickets
  • Emails
  • Meeting transcripts
  • Knowledge base articles
  • Customer feedback

By combining both, an organization can ask questions such as:

Why are sales declining for Product A?

The AI can correlate:

  • Revenue trends
  • Marketing performance
  • Customer complaints
  • Support tickets
  • Social media sentiment

All within a single conversation.

Real-World Use Cases

Sales Analytics

Ask:

Which region generated the highest revenue last quarter and how are we pacing this quarter?

Marketing Performance

Ask:

Which campaign produced the best lead to revenue conversion rate?

Customer Support

Ask:

What are the most common complaints this month?

Key Takeaways

After building my first Snowflake CoWork agent, here are my biggest observations:

Extremely Fast Setup

The quickstart guide gets you running within minutes.

Business-Friendly Experience

Users interact through conversation rather than SQL.

Transparency

The platform clearly shows what data sources were used.

Strong Foundation for Enterprise AI

By combining semantic models, Cortex Analyst, Cortex Search, and Snowflake Intelligence, organizations can create powerful AI assistants on top of governed enterprise data.

Final Thoughts

Rather than navigating dashboards, reports, and database schemas, users can simply ask questions and receive answers grounded in enterprise data. Deploying agents has already reduced dashboard requests.

My first experience building the Sales_AI agent showed how quickly we can assemble raw data to AI-powered business insights.

As Snowflake continues investing in semantic models management, Cortex Analyst, Cortex Search, and Intelligence Agents, the future of enterprise analytics is becoming increasingly conversational and approachable for non-BI / analytics professionals to build.

If you’re already using Snowflake, I highly recommend spending an hour with the Snowflake Intelligence quickstart. It’s one of the fastest ways to understand where AI-powered analytics is heading. For more learning how to move from testing to production, check out some of the real-world production learnings deploying Snowflake Agents.

Understanding Common AI BI Challenges that Slow Adoption

AI BI Challenges - Illustration of two profiles facing each other with a central data exchange, labeled Revenue in Business and Revenue in Data/System, symbolizing data flow.

We have 3 years of success and failure delivering LLMs on the back of 20+ years of delivering analytics. Our team is very bullish on AI because it’s grounded on years of success, but that comes with real AI BI Challenges. Understanding your organization’s dynamics are important to avoid them.

AI BI Challenges - Illustration of two profiles facing each other with a central data exchange, labeled Revenue in Business and Revenue in Data/System, symbolizing data flow.

Enterprise dynamics that can impact your AI BI success

Uniqueness

LLMs are trained on a corpus of knowledge that is wide reaching. For example, an LLM understands all facets of a general retail store operation. However, it does not understand your inventory and supply chain management, and customer buying patterns. These are nuanced problems that have required some form of AI. Your business is unique… Maybe it’s part of your secret sauce to success or maybe your uniqueness is holding you back. Like the team members that manage your business, AI requires direction from anything that breaks for “norms.”

Ambiguity

Ambiguity causes human confusion requiring “alignment.” We call bad output from an LLM a hallucination which ambiguity can easily trigger. In the workplace, tribal knowledge typically reduces ambiguity and fills in gaps. To be in the business of controlling the quality of AI / BI is removing ambiguity from data driven decisions. A process that is well defined documented and followed is easy to explain to people and a system. The “gray” area or human reasoned connections are both an incredible use case for using AI reasoning models but a very painful way to experience AI aided decision support. Data influenced decisions should be clear and consistent to be trust worthy.

Business Semantics Disconnect

Two team members show up to a meeting with 2 versions of revenue… This problem is painful, but often overblown to sell software and services. We understanding how decisions happen, semantics break down, and how to design systems that surface these disconnects early and often. All paths lead back to “governance” of some form, but we believe governing your semantics is just as important as the data itself!

Inconsistency

Consistency over time wins. This is especially true when your enterprise does not operate at high data volumes. A process that is well defined, documented, and followed is easy to explain to people and to a system. The gray areas and human-reasoned connections are both a powerful use case for AI reasoning and a painful way to experience AI-aided decision support. Data-influenced decisions need to be clear and consistent to be trustworthy.

Want to avoid AI BI challenges?

The businesses that win are not the ones with the most data or the most tokens burned on AI services . They are the ones who understand how to focus their teams on the right problems, AI BI concepts, and make consistent improvement to effectively use data for continuous improvement. DataTools Pro is here to help!

Build with React in Salesforce : Compensation Management DataTools Conversion

Hand holding a smartphone showing charts, with Salesforce cloud logo and React logo in the background, symbolizing mobile analytics and development.

This month, I took on a new feature released by Salesforce for multi-platform support. The support for React in Salesforce and now headless for AI apps is very exciting news. Now, our modern agentic workflows can connect and speed up deployment inside of Salesforce. I wanted to see how fast I could deploy our compensation management DataTools inside of Salesforce. Within a couple of days of experimentation, we have a fully custom React application directly inside Salesforce Lightning Experience, connected to live Salesforce data.

Here’s what I built, how it was built and how it will shape the value engineering work I do for clients using Salesforce.

New React dev for an existing app: Commission Management

Months ago, we built our own Commission Management DataTools that runs natively inside Salesforce. It was an internal R&D project that I built after building a client MVP. Commission management is actually a data and analytics first initiative that is wrongly scoped and built. Most companies build compensation in Excel. Many companies have solved this problem and Salesforce acquired one of them called Spiff. The level of effort to build these solutions requires deep domain expertise, and we have it in house.

Our commission management app includes:

  • Payout List View: A clean table showing payout records with status badges, amounts, and one-click navigation
  • Payout Detail View: A detailed breakdown of each payout, including calculations, attainment, team metrics, and final payout amount
  • Payout Entry Form: A smart two-step form that loads the correct fields based on the selected compensation plan, then creates the record directly in Salesforce
  • Commission Reporting: Simple Salesforce reporting and dashboards for sales and finance

Rebuild in React

The question was could we vibe code the same solution in React and run it inside of Salesforce? After a short learning curve, the answer is yes!

Payout List page:

Payout Management dashboard for User User - Enterprise_PM_Comp showing overview and calculations sections.

The Technology Behind React in Salesforce: Salesforce UIBundle

This project was made possible by Salesforce UIBundle, a relatively new and still evolving feature that allows you to deploy a React app as a Salesforce metadata component. Instead of hosting the app on an external server, it lives inside the Salesforce org and is served directly through Salesforce Lightning Web Runtime.

The app was built with:

  • React and TypeScript
  • Vite for front-end builds
  • Tailwind CSS for styling
  • Salesforce @salesforce/sdk-data for working with live Salesforce data

The Data Layer: GraphQL for Reads, Apex REST for Writes

One of the most interesting parts of this project was designing the data layer for a React UIBundle app inside Salesforce.

For reading data, we used Salesforce’s UI API GraphQL endpoint, the same technology that powers much of Lightning Experience. That gave us a clean and efficient way to query payout records and related values.

For writing data, we discovered that the GraphQL mutation route had limitations when working with custom object fields. To solve that, we used authenticated calls to a lightweight Apex REST API. The Apex controller accepts writable fields dynamically, handles type coercion, and avoids hardcoded field mappings.

That combination turned out to be the most reliable and maintainable approach:

  • GraphQL for reads
  • Apex REST for writes

GraphQL query code example

graphql
query GetPayouts {
  uiapi {
    query {
      Sales_Payout__c(first: 50) {
        edges {
          node {
            Id
            Status__c { value }
            Final_Payout_Calc__c { value }
          }
        }
      }
    }
  }
}

A Plan-Driven Entry Form – Meta Data powered Apps

Our compensation payout entry forms are now fully meta-data plan-driven. This approach puts the power in business leader’s hands where changes can occur monthly.

Each compensation plan, such as Senior Sales Director, has its own data entry configuration stored in a custom Salesforce object called Payout_Plan_Field_Config__c.

When the user selects a plan and clicks Next, the app retrieves that configuration in real time and renders only the relevant fields for that plan. There is no hardcoding and no need to rebuild the app when plan requirements change.

The only remaining step for Salesforce admins is adding or remove fields for a plan directly in Salesforce, and the form updates automatically.

Payout_Plan_Field_Config__c records in Salesforce:

The Full Salesforce Metadata Stack

Behind the React UI, we still get to work with a strong Salesforce foundation. As an example, our commisison app still has:

  • Custom Object: with fields for payout data, calculations, and status tracking
  • Custom Object: for plan-driven field definitions
  • Custom Metadata Types: metadata records for plan and tier configuration
  • Apex Classes for calculations, entry handling, plan administration, and reporting support
  • Apex Trigger: to run automatic calculations on save
  • Lightning App: with dedicated tabs
  • Flex Pages and Layouts for native Lightning integration
  • Lightning Web Components Now we can design the best solution for the job to re-evaluate our lighting web components and pages for for payout, statements, and analytics.
  • Permission Set for secure field-level access across custom objects

Everything was built using Salesforce DX, managed in Git, and deployed using a scratch org workflow.

Why This Matters for Salesforce Teams

This project proves an important point: you do not need to leave Salesforce to deliver a modern, highly customised application experience.

For organisations that need:

  • A custom applications built directly on top of the Salesforce relational model.
  • A React-based UI with a modern user experience inside Lightning Experience
  • Full control over their data without external SaaS tools, sync issues, or extra licenses
  • A configurable system that admins can manage themselves
  • Agentic workflows tools, and automation is adaptable to Salesforce.

We can’t wait to see how this evolves and materializes with Salesforce own native builder / vibe coding capabilities vs Cursor and Claude code solutions I use today.

  • Commission tracking
  • Team and AI agent onboarding workflows
  • Custom approval interfaces
  • Advanced analytics exploration tools (not to be confused with dashboards / BI)
  • Client-facing internal tools
  • Salesforce-native operational apps

Want Something like this in Your Salesforce Org?

If you are using Salesforce and have been told, “That is not possible in Lightning,” or “You need a third-party tool for that,” it may be time for a different conversation.

At DataTools Pro, we specialise in building advanced Salesforce solutions that stay maintainable, secure, and fully aligned with your existing Salesforce investment.