How to Send Personalized Tracked Links to 500 Prospects in 5 Minutes

Ilya SpiridonovIlya Spiridonov
··11 min read

If you're running cold outbound at scale and sending the same generic tracked link to every prospect on the list, you have an attribution problem. When someone engages with the content, all you see is an anonymous engagement event: a click, a view, some time-on-page, maybe a city from IP geolocation. No recipient identity. Nothing you can act on without guessing.

The fix is personalized tracked links: a unique URL per recipient (or per recipient company, for ABM) instead of a shared link to a list. Either resolution works. Per-contact links attribute the engagement to the specific buyer by name; per-company links attribute it to the account. Both are real first-party intent signals, in stark contrast to the generic-link case where you have nothing nameable. The tradeoff is volume. Generating 500 unique links one at a time isn't realistic when your sequence ships tomorrow morning.

This post is the walkthrough for the bulk path. Upload a CSV, map the columns, generate one tracked link per row, drop the resulting list into your cold-email tool. Five minutes from CSV to live campaign. The longer thesis on why personalized attribution matters for outbound is in Engagement Comes Before the Reply; this post is the operational how-to.

Generic shared link, sent to a list: when someone clicks, you see an anonymous engagement event with no recipient identity. IP geolocation can give you a city, sometimes a guess at the company if you've layered on an enrichment service, but neither is reliable enough to act on. Personalized link, sent to the same list: when someone clicks, you know exactly who engaged, when, for how long, on which page. Per-contact links name the buyer (call John); per-company links name the account (call into TechCorp). Both are usable first-party intent signals; both make the engagement cohort framework possible at the resolution your motion needs. Per-contact is sharper for SDR-led outbound to named buyers; per-company is more practical for ABM where the buying committee isn't fully mapped yet. The contrast that matters is personalized vs generic, not contact vs company. (Background on why first-party signals beat third-party data: First-Party vs Third-Party Intent Data.)

What you need before you start

Five minutes assumes you have all of this in hand:

  • A CSV of prospects. Minimum: a name and an email per row (for contacts) or a company name and domain per row (for companies/leads). Optional: any extra columns will be ignored at import; mapping is per-column, not per-row.
  • The asset you're sending. PDF, PowerPoint (.pptx), Word (.docx), HTML upload, or a Google Slides / Google Docs link. HummingDeck supports all of these as the underlying tracked document.
  • A HummingDeck account (the bulk import flow lives inside the deck detail page; see step 1 below).
  • Your cold-email tool. Smartlead, Instantly, Lemlist, Apollo, Outreach, Salesloft, anything that supports merge-field substitution. HD doesn't send the email; it generates the trackable links you put IN the email, sent via your existing cadence tool.

If you're missing any of these, the walkthrough below stalls. Worth taking 60 seconds to confirm before opening the modal.

The walkthrough (4 steps)

Open the deck you want to send. Open the bulk import flow. The modal is titled "Bulk Link Generation", with the subtitle "Import a list and generate share links for each entry." Four steps from here.

Step 1: Select type

Two options:

  • Contacts if your CSV is people: name + email per row. Each row gets a personal link tied to that contact, with the engagement attributing to them by name.
  • Companies (Leads) if your CSV is accounts: company name + domain per row. Each row gets a company-level link, useful for ABM where you want the link to follow the account but don't know specific contact names yet.

For most cold-outreach SDR work, Contacts is the right answer (you have the names). Pick it and continue.

Step 2: Upload the CSV

HummingDeck Bulk Link Generation modal, Step 2: Upload file. Drop zone for CSV upload. Footer note: 'Maximum 1,000 rows per import.'
Step 2: drop a CSV with a header row. Maximum 1,000 rows per import; for larger lists, split into batches.

Drop the CSV onto the upload area or browse to it. Plain CSV with a header row is the safest format. The current cap is 1,000 rows per import; for larger outbound lists, split into batches and run the import per batch (each batch produces its own export CSV that you concatenate before importing into your cold-email tool).

Common pitfalls to avoid:

  • Trailing whitespace in cells (the parser is tolerant but it's worth a quick clean)
  • Special characters in names (curly quotes, em dashes, accented letters) that some CSV exporters mangle on round-trip
  • Duplicate emails in the contact list (one tracked link per row regardless; you'll just generate duplicate links if you don't dedupe first)
  • Empty rows at the bottom of the file (Excel often adds these silently)

UTF-8 encoding is what most modern tools save by default, but if you exported from a legacy CRM, double-check.

Step 3: Map columns

HummingDeck Bulk Link Generation modal, Step 3: Map columns. Sample shows three CSV columns parsed: id, display_name, lead_name, description. Each column has a dropdown to map to a HD field (Ignore, Company, etc.). The display_name column is mapped to Company. Status indicator at the bottom: 12 rows ready to import.
Step 3: HummingDeck auto-detects which columns are which (email, company name, domain, first name, last name) and lets you override per column. The 'rows ready to import' counter at the bottom is the safest sanity check.

HD reads the headers and the first few rows of data, then auto-detects the type of each column. Recognized column types:

  • email (any column whose values look like email addresses)
  • companyName (headers like "Company", "Organization", "Account", "Business")
  • domain (values that look like website domains, e.g., acme.com)
  • firstName / lastName / fullName (headers + value patterns)
  • ignore (the default for anything unrecognized)

You can override any column's mapping via the dropdown if the auto-detect got it wrong. The "N rows ready to import" counter at the bottom is the most important sanity check on this screen: if it doesn't match the row count of your CSV, something didn't parse cleanly (usually empty rows at the end of the file, or a header row that wasn't detected).

Step 4: Generate

Hit Continue. HD generates one unique tracked link per row, bound 1:1 to that contact (or company, if you picked the company import type). For typical batches (50-500 rows) this completes in well under a minute.

When it finishes, you can download the resulting CSV. It's the same as your input file, with one new column added containing the unique tracked link for each row. That CSV is what you import into your cold-email tool.

After: setting up the merge field in your cold-email tool

What you have at this point is a CSV where every row carries the contact (or company) data you started with plus a column with that row's unique tracked link. The job now is to make that link addressable inside your cold-email tool's templating system, so a single email template generates 500 personalized emails on send. Three sub-steps:

  1. Import the new CSV into your cold-email tool or CRM as a prospect/lead list. The tool creates (or updates) one record per row. The new tracked-link column lands in a custom field on each record (different tools call this a custom variable, custom property, custom merge tag, or custom column, but the concept is the same: per-record data that lives outside the standard name/email/company fields).
  2. Confirm the column landed in a custom field. Smartlead, Instantly, Apollo, Outreach, Salesloft, and Lemlist all support custom fields. Each tool exposes its own template syntax for referencing them, typically a {{ }} placeholder referencing the column name. Check your tool's docs for the exact pattern.
  3. Insert the merge field in your email template where you'd normally paste a link. The template stays singular; the substitution happens per-recipient at send time, so each email lands in the inbox with that recipient's unique tracked URL embedded in the body.

The result: one campaign, 500 emails sent, 500 different personalized links inside them. No per-message manual work. No accidental shared link. Each click that comes back is attributed to the specific record it was sent to, which is the entire reason this whole flow exists.

After that, you're back in your normal cold-email flow. The difference is what shows up in your engagement view afterward. (Two upstream concerns the rest of this post takes for granted: the email actually reached the inbox, and the open event you might also be tracking is a real human open. Both are increasingly broken in 2026; see email deliverability and why email read receipts don't work. Personalized tracked links sidestep the second problem because they capture engagement on your viewer, not via inbox-side pixels.)

What you'll see after

Per-recipient engagement attribution. Specifically:

  • Real-time email + Slack notifications when each individual contact opens the link, spends time on it, returns, or forwards it
  • Per-page engagement data attributed to the specific contact (Sarah viewed page 3 for 4 minutes; Mike opened and bounced after 8 seconds)
  • The stakeholder map (Pro and above) shows the per-account, per-contact engagement pattern as a visual graph, useful when multiple people from the same company end up engaging
  • Three-layer bot filtering automatically applied, so SafeLinks scans and link-preview bots don't show up as fake engagement events
  • Unique-viewer detection: when the contact you sent the link to forwards it internally, the previously-unknown viewer entering surfaces as a distinct event (a buying-committee expansion signal)

This is the data layer that makes engagement-triggered followup possible at scale. Without per-recipient attribution, you're stuck calling everyone or no one.

Use cases beyond cold outbound

The same bulk-import flow works for several adjacent use cases:

Dormant lead reactivation campaigns. Export "lost" deals from your CRM as a CSV, bulk-generate tracked links to a new piece of content (a fresh case study, an updated ROI model, a comparison page that addresses last-time's objection), and send via your cold-email tool. Watch which contacts re-engage. Same logic as cold outbound, applied to a list of people who've already interacted with you. Full thesis in reviving dead leads.

ABM campaigns. Take a target-account list, generate one personalized link per stakeholder per account, and send. The per-recipient attribution is what tells you which stakeholder at the account engaged, which is the entire point of account-based outreach.

Event follow-ups. Post-conference contact list, personalized link to the deck or the relevant case study, see who actually read it after the booth conversation faded from memory.

Each of these is a variant on the same core operation: list of people, one piece of content, per-person attribution.

What to do after the bulk send

The bulk send isn't the goal. The goal is acting on the engagement data the next morning.

Wait 24-48 hours after the send for engagement to accumulate. Then sort the prospects into three cohorts based on what they did with the content:

  • Hot: opened the asset and spent meaningful time, returned to it, or forwarded it (new unique viewer detected). Call + LinkedIn this week, reference what they engaged with.
  • Warm: opened but engagement was thin. Send a different-angle email within 5 days; not a "checking in" follow-up but a fresh angle.
  • Cold: no engagement at all. Drop from this sequence; try a different asset in 2-3 weeks.

The full framework, with thresholds and the rationale for each cohort, is in Engagement Comes Before the Reply. The bulk send is what makes that cohort logic possible at scale. Without personalized attribution (per-contact for SDR-led outbound, per-company for ABM motions), you can't tell who's in which cohort, and the framework collapses back to "call the people who replied" (which is what you were already doing).


Related: