Back to Blog

How to Send a Trial-Ending Email Once Per Company

8 min read
How to Send a Trial-Ending Email Once Per Company

A trial-ending email is one of the highest-value automated emails a B2B SaaS company sends. It is also one of the easiest to get embarrassingly wrong, because the natural implementation sends it once per contact and your trials are shared by whole teams.

Five people in a workspace means five copies of "your trial ends in 3 days." Four of those people cannot add a credit card. At best it is noise. At worst it is a support ticket from the owner asking why you emailed their whole team about money.

This guide walks through sending exactly one trial-ending email per company, to the person who can act on it. It takes four steps.

Step 1: Upsert the workspace as an account

Whenever a workspace is created, upgrades, or changes trial dates in your app, upsert it. The externalId is your own org id, so this is idempotent and needs no mapping table.

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

Keep the attribute set small. Plan, seats, and trial end date are enough to drive this whole flow. From the terminal the same thing is one line:

sequenzy accounts upsert org_123 --name Acme --attr plan=trial --attr seats=5

Step 2: Attach members with roles

The email needs to reach the owner, so the platform has to know who that is.

POST https://api.sequenzy.com/api/v1/accounts/org_123/members
{ "email": "jane@acme.com", "role": "owner" }

Roles are owner, admin, or member. Set them from whatever your app already calls them; if your product has a single "workspace admin" concept, map it to owner so billing email has a target.

You do not have to enumerate members up front. Person events can carry an account reference, and the membership is created for you the first time a teammate does something:

POST https://api.sequenzy.com/api/v1/subscribers/events
{
  "email": "sam@acme.com",
  "event": "project_created",
  "account": "org_123"
}

If you have been sending events with a workspaceId or organizationId property, the setup detection scan will find them and offer a one-click backfill that creates accounts and memberships from your existing history, so step 1 and step 2 may already be most of the way done.

Step 3: Fire the account event to owners only

Three days before the trial ends, send an account event rather than a person event. This is the step that decides who is in the room.

POST https://api.sequenzy.com/api/v1/accounts/org_123/events
{
  "event": "trial_ending",
  "properties": { "daysLeft": 3 },
  "recipients": "owners"
}

recipients accepts four values, and picking the right one is most of the design work:

ValueWho is triggeredUse it for
ownersMembers with the owner role (default)Trial ending, payment failed, renewal
adminsMembers with the admin roleSeat limits, permission changes
allEvery member of the accountProduct launches, incident notices
noneNobodySignals you want on the timeline only

Whichever value you choose, the event is recorded on the account timeline, so you always have an auditable record of what the company was told and when.

Step 4: Enroll the sequence once per account

The recipients setting narrows who triggers the sequence. Per-account enrollment guarantees that even if two owners exist, or the same owner triggers twice, the company only runs the sequence once.

Use the existing matching-field enrollment mode on the sequence and set the field to account.externalId. The sequence then allows one active run per distinct external id.

SettingValueWhat it prevents
Triggertrial_ending account eventPerson-level triggers firing per seat
RecipientsownersMembers receiving billing email
Enrollment modeMatching fieldDuplicate runs from one company
Matching fieldaccount.externalIdDeduping on the wrong key

Together these give you one run, one recipient, one email per company.

The email itself

Because the account object is available, the copy can talk about the workspace rather than the reader. Merge tags {{account.name}}, {{account.domain}}, {{account.externalId}}, {{account.memberCount}}, and any account attribute such as {{account.plan}} all render in campaigns, sequences, and previews.

Subject: {{account.name}}'s trial ends in 3 days
 
Hi {{first_name}},
 
Your Acme workspace trial ends on {{account.trialEndsAt}}. {{account.memberCount}} people have been using it, and everything they have created stays exactly where it is when you add a card.
 
[Keep {{account.name}} running]
 
If now is not the right time, you do not need to do anything. The workspace moves to read-only and we will keep your data for 30 days.

Questions about billing or seats? Just reply to this email.

Two details worth copying. First, {{account.memberCount}} does real persuasive work, because "3 people have been using it" is a far stronger argument than a generic feature list. Second, the email is addressed to one person and speaks about the workspace, which is only possible when the account and the contact are separate objects.

Verifying it before the next cohort

Before you trust this in production, check three things.

Confirm the roles are populated. Run sequenzy accounts get org_123 --json and look at the members list. An account with no owner will silently email nobody.

Confirm the enrollment key. If you set the matching field to a contact-level field by mistake, you get per-seat sends again and the symptom looks identical to having no dedup at all.

Confirm the send in a preview. Account merge tags render in previews, so you can see whether {{account.name}} resolves before anything reaches a customer.

Once that is set, the same pattern generalizes. payment_failed to owners with per-account enrollment is a dunning sequence. seat_limit_reached to admins is an expansion nudge. You built the plumbing once.