Back to Blog

Account-Based Email for B2B SaaS: Stop Emailing Every Seat

7 min read
Account-Based Email for B2B SaaS: Stop Emailing Every Seat

Here is a scene that plays out in most B2B SaaS companies. A workspace signs up, five people join it over two weeks, the owner puts a card down, and the product starts working. Meanwhile the email platform sends all five of them a "still deciding? here's a discount" email, because as far as it knows, five separate individuals each started a trial and only one of them converted.

The bug is not in the sequence. It is in the data model. Your product sells to companies. Your email platform stores people. Everything downstream inherits that mismatch.

Three failures you have probably already hit

Teammates get treated as new leads. Someone joins a workspace that has been paying for six months. They arrive in your email platform as a fresh contact with no tags, no plan, and no history, so they land in the trial nurture sequence. Your best customer's newest hire gets a pitch for the product they already use.

Plan changes need N writes. A workspace upgrades from trial to pro. If plan lives on the contact, you now loop over every member and write the same value. If the loop half-fails, or a member joins after the upgrade, you are left with contacts in the same company holding different plans. Nobody notices until someone gets a wrong email.

Some questions are simply unaskable. "No one in this account logged in for 14 days" is not a statement about a person. Neither is "this workspace is at its seat limit" or "this company's card just declined." With a contact-only model you either approximate it per contact and get it wrong, or you build the query in your own warehouse and give up on triggering email from it.

Question you want to answerContact-only modelAccount model
What plan is Acme on?Copied onto 12 contacts, 2 are staleOne attribute on the account
Who should get the billing email?A billing tag someone remembers to setaccount.role is owner
Did anyone in Acme log in this week?Not expressibleAn account event or account attribute
How many people are in Acme?A count you maintain yourself{{account.memberCount}}
Acme upgraded, update everythingLoop over every memberOne upsert

What an account object actually holds

An account is small on purpose. Four parts do most of the work.

Identity. Your own externalId (the org id from your database), a name, and optionally a domain. Using your id means no mapping table and no ambiguity when two customers are both called Acme.

Attributes. A handful of company facts: plan, seats, MRR, trial end date, a usage number or two. These belong to the org, and a good platform fans them out to every member so they remain usable in segments and merge tags without per-contact writes.

Members with roles. Contacts attach to the account as owner, admin, or member. A contact can belong to more than one account, which matters more than people expect: agencies, consultants, and anyone who runs a personal workspace alongside a work one.

Account events. Events about the org rather than a person: trial_started, plan_changed, seat_limit_reached, payment_failed.

In Sequenzy an upsert looks like this:

POST /api/v1/accounts
{
  "externalId": "org_123",
  "name": "Acme",
  "domain": "acme.com",
  "attributes": { "plan": "pro", "seats": 12, "trialEndsAt": "2026-09-20" }
}

One call. Twelve contacts now segment on account.plan is pro and can render {{account.name}} in a subject line.

Roles decide who actually receives the email

This is where account-based email stops being a modeling exercise and starts saving you from angry replies. When you send an org event, you choose the recipients:

POST /api/v1/accounts/org_123/events
{
  "event": "payment_failed",
  "properties": { "attempt": 2 },
  "recipients": "owners"
}

recipients can be owners (the default), admins, all, or none. A declined card goes to owners. A seat limit warning can go to owners and admins, because both can usually fix it. A "new feature shipped" announcement goes to all. And none still records the event on the account timeline, which is the right choice for signals you want to segment on later but never want to email about.

EventSensible recipientsWhy
trial_endingownersOnly the owner can enter a card
payment_failedownersBilling problem, not a team problem
seat_limit_reachedadminsAdmins invite people and add seats
plan_changedallEveryone's limits just changed
usage_spikenoneUseful for segmenting, not for sending

Compare that to the tag-based workaround: a billing_contact tag that someone sets manually, drifts when the owner leaves the company, and silently sends dunning emails to an intern.

Getting there without a migration project

The objection to any new object is the sync work. In practice most of the data is already flowing. If you send events with a workspaceId, organizationId, or teamId property, you have been describing accounts by accident for years.

Sequenzy scans existing events for exactly those org-like properties and offers a one-click backfill that creates the accounts and memberships from your history. That gets you a populated account graph before you touch your application code. From there the incremental work is small:

  1. Upsert the account when an org is created or its plan changes.
  2. Include an account reference on person events so memberships create themselves as new teammates appear.
  3. Move your billing-adjacent triggers from person events to account events with recipients: owners.

Do those three and the failure modes at the top of this post stop happening, not because you patched each sequence, but because the data finally has the same shape as your product. From there you can build the segments and per-account sequences that were previously out of reach, such as enrolling one email per company instead of one per seat.