Goal: every Finance persona at a paying, Shopify-billed, monthly customer gets a plain-text email explaining why their company uses Littledata, why a peak-season surge makes now the time to switch, and what an annual contract saves - and switches by replying, after which we send an annual contract to pay by invoice. A separate one-off email, Email C, goes to the stores the series never reaches: a retired plan whose cheapest current plan billed monthly saves them money, with no annual offer to make.
Intercom has no API for Series, so the series is built in the Intercom UI. Everything it reads is written by this repo:
| Written by | What |
|---|---|
syncIntercomAnnualOfferAttributes (daily, after syncCustomersCron) |
the annual_offer_* and monthly_offer_* company attributes below |
pushCustomerLeadsToIntercomCron (daily) |
the Persona: Finance / Persona: Founder contact tags, on every such person the leads database knows at the customer, whether or not they log in to Littledata; and the Primary app user tag on the customer’s most recently seen login (services/contacts/primaryAppUser.ts) |
| ld-shopify | phase, is_annual, pay_by, plan_name, monthly_orders, tier_limit, complete_connections |
syncIntercomRoiAttributes (each value compute) |
roi_headline, roi_explanation - why the company uses Littledata, in its own numbers |
Shopify-billed, monthly, paying, external stores - priced against today’s list (Flex $0 + $0.35/order, Scale $199 for 1,500 + $0.15, Plus $990 for 10,000 + $0.03) at 20% off the base on annual billing:
| Pool | Stores | Would save | Saving/yr | Median saving |
|---|---|---|---|---|
| Live plan (Scale 38, Plus 8, Flex over ~455 orders) | 89 | 81 | $140k | $478 |
| Retired plan (Standard_2025 168, Pro_2025 86, Standard 56, …) | 432 | 255 | $319k | $910 |
A retired-plan store saves only when a live tier is cheaper at its volume - 177 of the 432 (most of
Pro2025, all of Enterprise_Plus_2025) would pay _more on any live tier and carry no offer (every
annual_offer_* key blank), so the series never reaches them. Flex stores below the Flex/Scale break-even (455 orders a
month) get no offer for the same reason.
annual_offer_*)All from buildAnnualOfferAttributes. Money is whole USD (Intercom renders a float as 159.2); annual_offer_overage_fee is the exception. Every key is blanked (null) when a
store stops qualifying - Intercom merges updates - so “no offer” is annual_offer_plan being
unknown, and that is the one key the audience should test for presence.
| API name | Meaning |
|---|---|
annual_offer_plan |
The live tier quoted (Scale, Plus, occasionally Flex): the cheapest one at last month’s volume once its base is discounted - always Scale or Plus; Flex has no base price so is never quoted |
annual_offer_plan_changes |
true when that tier is not the plan the store is on (every retired plan; Flex→Scale and Scale→Plus on live ones) |
annual_offer_legacy_plan |
true when the current plan is not on the price list - the invoice has to name a live tier first, so this picks Email B |
annual_offer_current_monthly_cost |
What the store paid for last month’s orders on its current plan, base + overage |
annual_offer_monthly_equivalent |
The same orders on annual_offer_plan billed annually: base × 0.8 + overage at list |
annual_offer_annual_price |
The invoice: annual_offer_plan base × 12 × 0.8. Overage is billed on top, at annual_offer_overage_fee |
annual_offer_annual_total |
monthly_equivalent × 12: what the year comes to at current volume, plan plus overage. Quote this beside the invoice amount, or a heavy-overage store reads $1,910 and pays $8,700 |
annual_offer_saving_per_year |
(current_monthly_cost − monthly_equivalent) × 12. Can be 0 when only the peak case saves |
annual_offer_included_orders_per_year |
annual_offer_included_orders × 12 - the year’s allowance on an annual contract, which is the peak-season argument: a surge draws on the allowance rather than being charged as overage that month |
annual_offer_bill_moves_with_orders |
true when the current plan would charge more at 1.5× last month’s orders (PEAK_SEASON_MULTIPLIER). false on a fixed-price plan - most retired ones - which is the condition for hiding the peak paragraph. The multiplier decides nothing else: an offer needs a positive saving at today’s volume |
annual_offer_*_text (current_monthly_cost, monthly_equivalent, annual_total, saving_per_year, included_orders_per_year) |
The same numbers as strings with thousands separators (18,000). Intercom prints an integer attribute as 18000, so the emails use these; the integer twins stay for segment rules |
annual_offer_included_orders, annual_offer_overage_fee |
The quoted tier’s included orders and per-order rate above them |
The customer document records whether either family is on the company (annualOfferOnIntercom,
annualOfferSyncedAt) so a store is blanked once, not nightly.
monthly_offer_*)From buildMonthlyOfferAttributes: the cheapest live tier at its monthly list price (no
discount, Flex included), for the same stores the annual family quotes - paying, Shopify-billed,
monthly. Set only when the switch saves at least $25 and 10% a month, the same rule
(cheaperLivePlan in planCost.ts) that puts the “switch plan” line on the value banner, so the
banner and the email agree. A store already on its cheapest live tier has the family blank. It does
not depend on the annual offer: a quiet retired-plan store whose best plan is Flex has a monthly
offer and no annual one.
| API name | Meaning |
|---|---|
monthly_offer_plan |
The live tier quoted (Flex, Scale, Plus). Test this for presence |
monthly_offer_legacy_plan |
true when the current plan is not on the price list: once they move, they cannot move back |
monthly_offer_current_monthly_cost |
What the store paid for last month’s orders on its current plan - the same figure as annual_offer_current_monthly_cost |
monthly_offer_monthly_cost |
The same orders on monthly_offer_plan at list: base + overage |
monthly_offer_saving_per_month |
The difference. Always at least $25 |
monthly_offer_included_orders, monthly_offer_overage_fee |
The quoted tier’s included orders and its first per-order rate above them |
monthly_offer_plan_terms_text |
The plan in words: $199 a month including 1,500 orders, then from $0.15 an order, or $0.35 an order, with no monthly fee |
monthly_offer_*_text (current_monthly_cost, monthly_cost, saving_per_month, saving_per_year) |
Formatted twins, as for the annual family. saving_per_year_text is the monthly saving × 12 |
Related, written by ld-shopify: offer_annual_offer and offer_*_price - a 30% discount on the base
price only, live plans only, no overage. The series should not read them; Shopify applies 20%.
Subscription type. Create an opt-out type “Account offers” (Settings → Email → Subscriptions) and put both emails under it. Do not reuse “Monitor AI monthly summary”: someone who unsubscribes from the ROI email has not unsubscribed from their account manager.
Audience - a dynamic segment, so a store joins the day its numbers say it saves and leaves the day they stop. Type is Leads and Users: the lead push tags a finance person who already logs in to Littledata on their user contact, and a Leads-only audience never sees them. Entry rules (contact-level, checked continuously):
Persona: Finance or Persona: Founder or Primary app user - its own
OR group, joined to the company rules with AND. The primary app user is the store’s most recently
seen Littledata login who is not agency staff (syncCustomerAgenciesCron knows them by email
domain): at a store with no finance or founder on file, that is the person who runs the account, and 145 of the 147 offered companies without one have such a login.
Where a company has both, both get the email. Founder was added on 2026-10-01: 174 of the 210 offered companies the
leads database can resolve have no finance title on file at all (small stores, 0 to 7 people
found), and at that size the founder pays the Shopify bill. Where both exist, both get the emailannual_offer_plan has any value (the offer exists; every other annual_offer_* key is set with it)phase is paying, is_annual is false@littledata.ioannual_offer_saving_per_year greater than 100, so the number is worth a replyannual_offer_annual_total greater than 1900: no annual contract for a brand that would
pay much under $2,000 a year. Scale with no overage is $1,910, so this keeps every Scale offer and
the floor only bites if a cheaper tier is ever addedmonthly_orders greater than 1499 (added 2026-11-03). The dollar floor cannot screen out
small stores: every Scale offer under the 1,500-order allowance is the same $1,910, whether the
store does 7 orders a month or 1,400. Of the first 25 replies, the stores that converted or asked
to were mostly at 1,000-3,000 orders a month, while Himla (146) and RITE Energy (7) were quoted
the same $1,910 contract. Raising the dollar floor instead would cut real wins: $2,500 needs
~1,830 orders on Scale, $3,000 needs ~2,110With every rule but the orders floor, 237 of the 240 offered companies qualified (September 2026: 160 retired-plan, 77 live). 104 of them also carry a monthly offer (October 2026, median $136 a month), but the series does not quote it: Email C goes only to stores outside the series.
Exit rules: company is_annual is true; or annual_offer_plan is unknown; or company
monthly_orders is less than 1500 (so a small store already in the series gets no follow-up); or the
contact replied to any email in the series (Intercom’s “replied” condition). Goal: is_annual is true.
Steps:
annual_offer_legacy_plan: false → Email A, true → Email B.Annual offer: no reply and exit, so the next wave can exclude them.Sender: a named account manager, not “Littledata”. Replies land in the Inbox; the reply is the
conversion - the account manager sends the annual contract, invoiced, and flips payBy in the admin.
Merge tags use `` with a fallback set in the composer for every
numeric field. Wrap Email A’s peak paragraph in a conditional block: show when
annual_offer_bill_moves_with_orders is true and annual_offer_saving_per_year is less than
1000 - where the saving is already large (a busy Flex store) the number makes the case on its own.
Email B has no peak paragraph: the point there is that they save by switching.
annual_offer_legacy_plan = false)Subject: 's Littledata cost, before peak season
Hi ,
uses Littledata to send complete, first-party order data from Shopify to
.
Last month that cost $ for
orders, billed month to month through Shopify.
[show when annual_offer_bill_moves_with_orders is true and annual_offer_saving_per_year < 1000]
That bill moves with your orders, so if you're expecting a peak-season surge, now is a good time
to switch to annual: the included monthly orders become an allowance for the year -
orders per year on
.
[end]
On an annual contract for , your current volume works
out at $ a month, which saves
about $ over the year.
At your current volume the year comes to $,
invoiced once. Nothing changes in the app.
If you'd like to switch, reply to this email and we'll send over an annual contract for you to
review.
When annual_offer_plan_changes is true (a Flex store moving to Scale), the second-to-last
paragraph already names the new tier; no separate wording is needed.
annual_offer_legacy_plan = true)Subject: 's Littledata plan: a cheaper way to stay on it
Hi ,
uses Littledata to send complete, first-party order data from Shopify to
.
You're on our plan, which we no longer sell. It carries on as
it is, at $ a month billed through
Shopify, but it can't be moved to annual billing - an annual contract has to be on one of the
current plans.
The good news is that the current plan, on an annual
contract, is cheaper for you:
orders a year included, and $ a month at
your current volume. Over the year that saves about
$.
At your current volume the year comes to $,
invoiced once. The app, your connections and your data don't change - only the plan name and the
bill.
If you'd like to switch, reply to this email and we'll send over an annual contract for you to
review.
Subject: Re: + the original subject
Hi ,
Quick one - the annual contract for would come to
$ a year at your current volume, against about
$ a year saved on the current
month-to-month bill. Happy to send it over if it's useful; just reply here.
A standalone Outbound email (dynamic audience, send once, during office hours), not a series step: “Nudge to latest pricing, for those with no annual offer”, live since 2026-10-05. It reaches the ~50 Shopify-billed monthly stores on a retired plan that have a monthly offer and no annual one - quiet stores whose cheapest current plan is Flex. The merchant switches themselves, in the app’s Billing page, because a Shopify charge needs their approval. Sent from “Edward from Littledata” under the Announcements subscription type.
Audience (Leads and Users, dynamic): company monthly_offer_plan has any value, annual_offer_plan
is unknown, monthly_offer_legacy_plan is true, phase is paying, is_annual is false; email does
not contain @littledata.io; person tag Persona: Finance or Persona: Founder or Primary app
user; not unsubscribed from emails or from Announcements; last contacted more than 2 days ago.
Subject: A cheaper Littledata plan for
Hi ,
Last month paid $ for
orders on a plan we no longer sell.
The same order volume on our current plan would have cost
$ - about $ a month
less, or $ over a year.
It's still billed monthly through Shopify, and includes all the latest features.
To switch, log in to the Littledata app, go to Billing and choose the new plan.
Regards,
Edward
The company is named with Intercom’s Company name, not plan_name: that attribute holds the
internal id (Standard_2025). A store doing one order a month is quoted “would have cost $0”
(Flex at $0.35 an order, rounded to whole dollars) - 2 of the 53 in October 2026.
npx tsx scripts/get-logs.ts syncCustomersCron 24 --filter "annualOffer" - the daily
[annualOffer] done: line gives offered / cleared / unchanged / errors.monthly_offer_plan has any value holds ~160 companies (October 2026): ~100
inside the annual audience, ~50 monthly-only (quiet retired plans whose best plan is Flex) - the
Email C audience.annual_offer_plan has any value holds ~240 companies (~80 with
annual_offer_legacy_plan is false, ~160 with it true). Each contact in the series should carry
Persona: Finance.is_annual on the next ld-shopify sync and is blanked by the
next night’s run.