Intelligent Campaigns: How Ruby turns research into personalization tokens

When we introduced Intelligent Campaigns we made an argument: the personalization in a campaign should live in the CRM record, not in the email copy. Store a value that had to be reasoned into existence, put it on the contact as a property, review it, then let the sequence read it like any other field.

That post was the framework. This one is the machinery. Intelligent Campaigns is now a feature in Ruby that produces those values at scale, and it is worth walking through exactly what it does, because the difference between a static field and a dynamic token is not a matter of degree. They are different kinds of data.

1. Two kinds of token

Every sequencing tool ships with mail-merge tokens. In HubSpot they look like this:

Hi {{ contact.firstname }},

I noticed {{ company.name }} is in the {{ company.industry }} space...

These tokens are lookups. The value already exists in a database column. The template reaches in and copies it. firstname was typed in by whoever created the contact. industry came from an enrichment vendor's taxonomy. Nothing was decided at merge time, so nothing in the output is specific to the reader beyond what a directory listing would show them.

Buyers learned to recognise this years ago. The tell is not the token itself; it is that the surrounding sentence has to be generic enough to be true for every possible value of the token. "I noticed you're in the software space" is a sentence written to survive any industry.

A dynamic token inverts the relationship. The value does not exist until something reads the available evidence about this person at this company and writes a sentence that could only be true for them. The property on the contact record is the same shape — a text field named ruby_pain_sentence — but what is inside it is a judgement, not a copy.

Here is the concrete difference, using an illustrative contact:

Static field

Value

contact.jobtitle

VP of Engineering

company.industry

Logistics software

company.numberofemployees

850

Dynamic token

Value

ruby_pain_sentence

You are being asked to ship the new carrier integrations faster while the platform team is still absorbing the March reorg.

ruby_role_context

As the person who signs off on what gets built this half, you decide whether integration work stays in-house or gets a partner.

ruby_company_context

The eleven open backend roles on your careers page suggest the roadmap is outrunning the team.

ruby_priority_signal

Your Series C in June came with a stated push into European freight, which means new carrier coverage on a clock.

ruby_recommended_action

Worth a 15-minute look before you lock the H2 integration plan?

Every sentence in the second table passes the four tests from the framework post. It was derived, not copied. It is sourced from something Ruby actually read. It is proprietary in the sense that a competitor pulling the same enrichment vendor will not have it. And it is perishable: the reorg, the hiring spike and the funding round will all stop being true, which is exactly why a human should review it before enrollment and why it should be regenerated when the account changes.

The rest of this post is about how those five sentences come to exist.

2. What a campaign is

An Intelligent Campaign is a named batch of up to twenty accounts that go through the same pipeline together. You give it a name (or Ruby derives one from the account names: a single account is named after itself, three or more become "Acme +2"), pick where the accounts come from, and the batch is persisted the moment accounts are selected so you can come back to it before research has started. Renaming works on the whole batch at once.

Accounts can come from three places:

  • HubSpot companies, picked from your connected portal.

  • Salesforce accounts, same idea.

  • A website or company name, for accounts not yet in your CRM.

A campaign can mix HubSpot and Salesforce accounts. Each account becomes one run inside the batch, with its own status, its own stakeholder roster and its own cost line, but the wizard shows them together and pushes them together.

The wizard walks seven steps: Source → Accounts → Memory → Personas → Research → Contacts → Sequences. The two that matter most for token quality are Memory and Personas, and they are the two most people skip, so they get their own sections below.

3. Where the evidence comes from

A token is only as good as what the writer was allowed to read. Ruby gives the token writer four inputs, and each one is a distinct source of specificity.

3.1 Account research

Research runs in two phases, and the split is deliberate. Phase one is five agents that have no dependencies on each other, so they run in a single parallel wave:

Agent

What it produces

Which token it grounds

Company 101

A fact sheet on the account: what they sell, to whom, how they are positioned

company_context, pain_sentence

Latest news

Recent public announcements, funding, launches, leadership moves

recent_event, company_context

Hiring trends

Open roles by function, with a one-line "so what"

priority_signal, company_context

Key people

The top stakeholders matching your target personas, each with a professional background

career_journey, role_context

Elevator pitch

Why your product for this account, drawn from your own website

Rep-facing summary; also keeps the seller straight

Phase one is what the tokens wait on. As soon as it finishes, Ruby drafts the tokens and the per-person talking points, then kicks off phase two in the background: industry analysis, competitor analysis, similar companies, 10-K analysis, workforce analysis, a discovery questionnaire, a meeting strategy and a tech landscape. Phase two enriches the account page and the later meeting brief, but you do not wait on it to get your outreach assets. The design goal was that a rep gets tokens in minutes, not after a full account workup.

While research runs the wizard shows per-account progress chips for each of the five phase-one sections, and you can ask Ruby to email you and ring the bell when the batch is done rather than watching it.

3.2 The stakeholder roster

Tokens are written per person, so who is on the roster matters as much as what is known about the company.

The roster is assembled from two directions. The Key People agent searches for stakeholders matching the personas you named (a handful of titles or categories; "VP Engineering", "RevOps", "Security") and writes a professional background onto each one it finds. Separately, for a HubSpot company or Salesforce account, Ruby pulls up to ten of the contacts already associated with it in your CRM and adds them as stakeholders, resolving each through the same email and name waterfall the account page uses. Those CRM contacts are the people you will actually enrol, so they must have tokens even if the persona search would never have surfaced them.

The two sources are labelled in the Contacts step (CRM-imported versus Ruby-found) so you can see which is which.

3.3 Your own knowledge

This is the institutional layer from the framework post, made concrete.

The Memory step lets you upload customer stories, battle cards, sales plays and similar material. There are two layers: organization memory, which an admin curates and every rep's campaigns draw from, and private memory, which is yours alone. A private document is only ever read for the rep who uploaded it. Nobody else's tokens see it.

At generation time Ruby pulls the relevant chunks from both layers into the prompt, budgeted to roughly eight thousand characters, and instructs the writer to ground pain_sentence and company_context in them where relevant. This is how a token can reference the fact that you have three logistics customers who solved this exact integration problem, which no enrichment vendor knows.

3.4 Who is selling

A small but critical input. The token writer is told, explicitly and first, who the seller is: your company website and description from your profile, plus the products attached to the account. Every token is framed as our outreach to this stakeholder, and recommended_action pitches our product.

This exists because early versions occasionally confused the prospect's own tooling with the seller's, particularly when the prospect sold software too. The seller context is a single shared helper used by both the token writer and the talking-points writer, so the two artifacts never disagree about which side of the table you are on.

4. How the tokens get written

With the evidence assembled, generation is one model call per batch of ten stakeholders, running against Claude Sonnet on Bedrock. A roster of up to twenty stakeholders becomes two concurrent calls. Batching exists because a single call's output budget cannot hold twenty stakeholders' worth of JSON without truncating, and a truncated JSON blob is worse than a smaller one.

Every call gets the same system prompt: the seller context, then the token rubric. The rubric is a single constant shared with the analyst chat, so a token written from a campaign and a token written by asking the analyst follow identical rules. The rules that matter:

  • Second person, always. Each line is written to the stakeholder ("you", "your") so it drops straight into an email. Never a note about the person, never an instruction to the rep.

  • One short sentence. Specific, plain, no fluff.

  • Never invent facts. If the pain is unknown, the line stays about the role rather than guessing.

  • Leave it empty if you have nothing specific. career_journey, company_context, priority_signal and recent_event are all allowed to be blank. A blank token is honest; a generic one is the thing we are trying to eliminate.

  • Most recent figure wins. If the evidence contains two valuations or headcounts, use the newer one and name its date.

Each stakeholder is sent with a short numeric ref, and the model echoes the ref back. That is how Ruby re-attaches the generated tokens to the right stakeholder even when the model reformats a name. A normalised-name match is the fallback.

The output is seven fields per person: the five canonical tokens (pain_sentence, role_context, recommended_action, career_journey, company_context) and two optional signals (priority_signal, recent_event). Ruby stores them on the stakeholder record, capped at 600 characters each and HTML-escaped.

In the same pass, for outreach campaigns, Ruby writes a call-prep sheet per stakeholder with a separate, larger call: likely buying role, bio bullets, a call opener, why-you-why-now points, three discovery questions, anticipated objections with handling, an account overview and a value prop. These are rep-facing rather than buyer-facing, and they read the same briefs and knowledge as the tokens, so the sheet and the tokens tell the same story.

4.1 What happens when generation fails

Model calls fail. Connections drop, Bedrock throttles, JSON comes back malformed. The system is built on the assumption that this will happen at the worst possible moment, and the rule is simple: placeholder text never reaches your CRM.

Concretely:

  • Every call retries up to four times with jittered backoff on transient errors. Validation and permission errors do not retry.

  • A failed batch does not lose the other batch's tokens. Each batch is independent.

  • If a stakeholder ends up with no generated tokens, the editor shows template text and marks the row as not generated. That row cannot be pushed. If you try, Ruby writes the generated ones, holds the placeholders back and tells you who was held and why.

  • While you are on the Contacts step, Ruby retries generation for placeholder rows in the background, at most every ten minutes and at most three times.

  • A Regenerate action drafts tokens and talking points for whoever still has none, at no ruby cost, because no research is rerun.

  • Contacts that arrive late (CRM seeding lands after the research completed) get the same fill pass automatically.

Every generation pass is recorded like an agent run: one execution row with token counts, cost and latency, and a trace carrying each call's prompt and reply. If a batch goes missing you can read the trace rather than the pod logs.

5. The review step

Tokens land in the Contacts step as an editable roster. Every row shows the person, their title, whether they came from your CRM or Ruby's search, a persona chip inferred from the title, and a review status. Expand a row and you get every token and the talking points, editable in place. Edits save automatically.

This step is not optional ceremony. It is where the "perishable" test gets applied by a human. The model does not know that the VP of Engineering announced their departure yesterday. You might. Fix the token, or uncheck the row.

You choose who to push. The default is everyone with generated tokens, but the selection is yours, and the push writes only the people you selected.

6. What lands in the CRM

This is where a dynamic token becomes usable by a sequence.

6.1 HubSpot

On push, for each selected stakeholder Ruby resolves the HubSpot contact (by stored ID, email, or LinkedIn URL), creates it if it does not exist and associates it to the company, and writes three things:

Contact properties. Each token becomes a text-area property in a Ruby Insights property group on the contact:

Token

HubSpot property

pain_sentence

ruby_pain_sentence

role_context

ruby_role_context

recommended_action

ruby_recommended_action

career_journey

ruby_career_journey

company_context

ruby_company_context

priority_signal

ruby_priority_signal

recent_event

ruby_recent_event

The properties are created on first sight, and the mapping is dynamic: any new key that appears on a stakeholder's token object becomes ruby_<key> without a code change. If your portal has not granted the schema-write scope the property creation fails soft and Ruby writes what it can rather than failing the push.

A Talking Points note. One note per contact on the timeline, listing every non-empty token under a human label. It is created once and then updated in place; a content hash prevents rewriting an identical note.

A Ruby account brief note on the company record, when the account-brief push is enabled for your workspace: the company point of view, the pitch, the latest news lead, the hiring signal, and the buying committee with a count of how many have tokens ready, linking back to the full research in Ruby.

6.2 Salesforce

Salesforce contacts get a Ruby Talking Points ContentNote linked to the Contact, with the same create-once-then-update behaviour. No custom fields are required, so it works on any org that already permits notes. Sequences live in HubSpot, so a Salesforce-only campaign ends at the Contacts step.

6.3 Using the tokens in a sequence

Once the properties exist on the contact, HubSpot treats them like any other contact property. In a sequence template they are ordinary personalization tokens:

Hi {{ contact.firstname }},

{{ contact.ruby_pain_sentence }}

{{ contact.ruby_company_context }}

{{ contact.ruby_recommended_action }}

Best,

{{ sender.firstname }}

Three points about writing templates against derived tokens, because they behave differently from static ones:

  1. Each token is a complete sentence. Do not wrap it in a sentence of your own. "I noticed that {{ contact.ruby_pain_sentence }}" will read badly, because the token already says "you are being asked to…". Put the token on its own line and let it carry the paragraph.

  2. Optional tokens need fallbacks. career_journey, company_context, priority_signal and recent_event are deliberately blank when Ruby had nothing specific. HubSpot's default-value setting on the token is where you put the fallback, and the fallback should be a sentence that reads naturally on its own, not "N/A".

  3. Coverage is the number to watch. The framework post defined coverage as the share of enrolled contacts with a researched value. Ruby reports this per account as tokens-ready over stakeholder count, both in the campaign summary and in the account brief note. If coverage is low on an account, the fix is upstream: better personas, more memory, or a regenerate pass, not a more forgiving template.

Ruby does not enrol contacts for you. The Sequences step opens HubSpot Sequences in your portal, shows how many contacts and accounts were pushed, warns you if nothing has been pushed yet (so a sequence would send without personalization), and asks you to confirm when you have enrolled. That gap is intentional: enrollment is the moment the outreach becomes real, and it should be a human decision made after the review step.

7. What it costs and where the money goes

An outreach campaign is priced per account at a flat catalog rate that covers the full research chain, the token generation and the call-prep sheets. Accounts whose research is already complete are not charged again; the quote step shows this before you start, and if the campaign cannot be started the charge is reverted.

Every model call in the pipeline is metered onto the account's cost summary alongside the research agents, so the per-account view shows what the research cost, what the token writing cost and what the CRM contact seeding cost in enrichment credits. Nothing about the pipeline is a black box on the bill.

8. Why this is not "AI-written emails"

It is worth ending on the distinction, because it is the whole point.

Ruby does not write your emails. It writes seven short, sourced, second-person facts about each person and puts them on the contact record where a human can read them, edit them, and delete them. Your sequence template is still yours. Your sending cadence is still yours. The decision to enrol is still yours.

What changes is what the template has to work with. A static field gives it a label. A dynamic token gives it a judgement, with a source behind it, that will go stale and that someone has looked at. That is the same thing a good rep does by hand for their top five accounts on a Monday morning. An Intelligent Campaign does it for twenty accounts and their full buying committees, and leaves the rep the part that actually requires a rep