A SaaS trial ending email should answer four questions: when does the trial end, what happens next, what will it cost, and what can the customer do? Use different copy for an automatic charge and an optional upgrade. Stop the sequence when the account changes.
Your trial user has built something useful. Now they need to decide whether to pay. An email that says only "Your trial is almost over" leaves them guessing about their work, the price, and whether their card will be charged. Those are the details to resolve before trying a more persuasive subject line.
This guide focuses on the last part of a SaaS trial: the decision to continue, cancel, or ask for help. For the earlier setup emails, use our onboarding sequence guide. Use the copy examples and starter schedule below to plan your flow. The AI-builder prompt and TypeScript examples cover who receives a reminder and what the message says.
| Account situation | Email job | Main action |
|---|---|---|
| Ready to continue, no automatic charge | Explain how to keep the work they started | Choose a plan |
| Still blocked during setup | Offer help with the unfinished task | Resume setup or reply |
| Will be charged after the trial | Give clear advance billing information | Review billing and cancellation |
| Already paid, canceled, or extended | Recheck the account before sending | Suppress or reschedule |
Choose the message from the account's current state, not just its signup date.
Start with what actually happens when the trial ends
Write one sentence describing the product's real behavior. For example: "At the end of the trial, this workspace becomes read-only until its owner chooses a paid plan." Or: "The selected monthly plan starts automatically unless the owner cancels before the trial ends." These require different emails.
Check a test account rather than relying on the pricing page. Find its trial end, selected plan, payment settings, and cancellation state. Also check your app's access rules. A billing provider changing a subscription does not, by itself, explain whether your app keeps files readable or disables a particular feature.
Stripe documentation checked October 9, 2026: the new Trial Offers API requires version 2026-09-30.endive or later. It applies introductory pricing to subscription items. Checkout currently uses subscription-wide free trial periods instead. Read the comparison of the two trial models before asking an AI builder to change an existing integration.
The code below assumes one subscription-wide free trial and one workspace billing recipient. It is not an item-level Trial Offers implementation. If an AI add-on expires while the base plan stays active, name that add-on and its affected features. Don't tell the customer their whole account expires.
With Stripe's subscription-wide free trials, a missing payment method can lead to cancellation, pausing, or invoice creation, depending on trial_settings.end_behavior.missing_payment_method. Stripe checks payment methods and legacy sources on both the subscription and customer. Confirm the configured behavior using the free trial documentation. "No card on file" is not enough information to promise that no invoice will be created.
Use a small schedule, then adjust it to the trial
For a 14-day, user-initiated upgrade trial, start with a decision email three days before expiry and one final reminder during the last day. Adjust this starting schedule to your product, and keep required billing notices on their own schedule. If your users decide quickly, one message may be enough.
| Suggested point | What belongs in the message | When to skip it |
|---|---|---|
| 3 days before expiry | Exact end date, relevant work completed, next action | Paid, canceled, or not eligible for promotional email |
| During the final 24 hours | Brief reminder of the same decision and access policy | Paid, canceled, trial extended, or support case needs a human reply |
| After expiry, if needed | What actually changed and how to return | No account change to explain, or recipient opted out of promotional follow-up |
A starting point for optional upgrade emails. Automatic billing notices need their own schedule.
A three-day trial should not inherit every message in a 14-day sequence. If someone signs up with fewer than 24 hours left, the final reminder is the only applicable stage. If your trial is measured in credits rather than days, trigger from the actual remaining allowance and explain the reset or purchase options. Don't invent a deadline for a credit balance that never expires.
Put the exact time and a named timezone in the email. "Tomorrow" becomes wrong when someone reads the message late. "October 23 at 16:00 UTC" stays understandable. Show local time if you have a reliable user preference; otherwise an explicit UTC time is better than guessing from an email address.
Keep Stripe's notices separate from your suggested cadence
Stripe's trial reminder guidance says its enabled customer reminders go out seven days before the trial ends, or at the start for shorter trials. Its trial_will_end event reference describes an event three days before the end, or when a trial ends immediately. An event arriving and a customer receiving an email are separate things in your own sending pipeline.
Open Stripe's Subscriptions and emails settings and record which reminders it owns. The simplest starting choice is to keep Stripe's billing notices enabled and make your app's optional messages about product use. If you replace provider notices, verify the applicable requirements and the replacement flow first.
Trial ending email examples you can adapt
These are fictional examples for a product called Northstar. Replace every bracketed value with verified account data. The access policy and pricing are examples too. Read the rendered email alongside the actual billing screen before approving it.
When the customer must choose a paid plan
Subject: Your Northstar trial ends on [date]
Preview: Choose a plan if you want to keep editing your workspace.
Hi [name],
Your Northstar trial ends on [date] at [time and timezone].
You created [verified number] reports in [workspace name].
To keep creating and editing reports, choose a paid plan before then.
If you don't upgrade, your workspace becomes read-only.
[Insert your verified export and data-retention policy.]
This trial does not automatically start a paid subscription.
[Choose a plan: link to your authenticated billing page]
If you're still deciding, reply and tell us what you need to check.
[Sender name]Remove the reports sentence when the count is zero or unavailable. For a shared workspace, address the person allowed to manage billing. A member who cannot select a plan needs a route to the workspace owner, not a checkout button that ends in an authorization error.
When the trial converts to a paid subscription automatically
Subject: Your Northstar paid plan starts on [date]
Preview: Review the plan, billing details, and cancellation options.
Hi [name],
Your trial ends on [date] at [time and timezone].
Your [plan name] subscription is scheduled to start after the trial.
[Verified billing summary: amount, currency, interval, and tax treatment.]
You don't need to choose the plan again to continue.
To review payment details or cancel before paid billing begins:
[Manage subscription: link to your authenticated billing page]
If you cancel before the trial ends, [verified access policy].
Questions about the plan? Reply to this email.
[Sender name]Avoid "Upgrade now" when the subscription is already scheduled to bill automatically. Give the customer a working management link. For usage-based AI products, a base subscription price may not be the final charge. Explain the included allowance and any variable charges; don't turn a pricing-page number into a guaranteed invoice total.
When the user never finished setup
Try a direct help message: "Your trial ends on [date]. Your data source is not connected yet, so you haven't been able to run a report. Open setup to finish the connection, or reply with the error you see." Link to that exact setup step. Check that the missing action is true, and don't guess why someone stopped.
If you can offer an extension, explain who qualifies and how to ask. Grant it only after updating the account's real expiry. An email promising extra time while the app locks access creates a support problem. Don't automate a discount until you understand whether price was the blocker.
During the last day
Keep it short: "A reminder that your Northstar trial ends on [date and time]. To keep editing, choose a plan here. If you do nothing, your workspace becomes read-only." Repeat the same policy as the earlier message. If you offered an extension or the user already subscribed, suppress this version.
Personalize with a fact the customer recognizes
"You created three reports" is useful when it is true. "You saved 12 hours" needs a defensible measurement. Use completed work to show what the customer can continue doing after upgrading.
Give your AI builder an account-aware brief
A founder using Lovable, Bolt, Cursor, or another builder can start with this prompt. Ask it to inspect the existing billing and email paths before adding a new one. Supply your approved copy and access policy; those are product decisions the agent cannot infer reliably.
Audit our existing trial-ending email flow before changing it.
Identify the trial model, billing recipient, expiry source, and every sender.
Tell me whether Stripe already sends trial-ending billing notices.
For optional upgrades, suggest one email 3 days before expiry and one
during the last 24 hours. Respect our promotional email preferences.
For automatic billing, retain the existing required notice schedule.
Don't change prices, trial length, or billing provider settings.
Re-read the account before sending. Suppress paid or canceled accounts.
Replan extended trials and never send an old queued reminder.
Use a persistent record so retries don't create another message.
Link to our app's sign-in-protected billing page. Only an authorized
billing owner may create a customer portal session on the server.
Show me the copy for optional upgrade, automatic billing, and setup help.
Include the exact expiry, timezone, real access policy, and billing summary.
List missing product decisions instead of inventing them.
Test upgrade, cancellation, extension, opt-out, and delayed delivery cases.
Report which checks passed and which provider checks still need testing.Review its answer for two details: where the current account state comes from, and what prevents an old queued job from sending. Ask for the account fixtures and send history that demonstrate both.
Make the send decision explicit in TypeScript
This small decision function handles optional upgrade reminders only. Keep automatic billing notices in the provider-owned or separately verified flow. Run it from a scheduled worker using a fresh account snapshot. All timestamps are Unix seconds, not JavaScript milliseconds.
export interface TrialSnapshot {
subscriptionId: string;
trialEnd: number | null; // Unix seconds
status: "trialing" | "paid" | "canceled" | "expired";
cancellationScheduled: boolean;
billingMode: "optional-upgrade" | "automatic";
promotionalEmailAllowed: boolean;
recipientDeliverable: boolean;
supportHold: boolean;
}
export interface ReminderDecision {
stage: "decision" | "final";
key: string;
trialEnd: number;
}
export function decideTrialReminder(
account: TrialSnapshot,
now: number,
): ReminderDecision | null {
if (!Number.isSafeInteger(now) || now < 0) {
throw new Error("now must be Unix seconds");
}
const end = account.trialEnd;
if (end === null) return null;
if (!Number.isSafeInteger(end) || end < 0) {
throw new Error("trialEnd must be Unix seconds");
}
if (!account.subscriptionId.trim()) {
throw new Error("Missing subscription identity");
}
if (
account.status !== "trialing" || account.cancellationScheduled ||
account.billingMode !== "optional-upgrade" ||
!account.promotionalEmailAllowed || !account.recipientDeliverable ||
account.supportHold
) return null;
const remaining = end - now;
if (remaining <= 0 || remaining > 72 * 60 * 60) return null;
const stage = remaining > 24 * 60 * 60 ? "decision" : "final";
return {
stage,
trialEnd: end,
key: ["trial-reminder", account.subscriptionId, end, stage].join(":"),
};
}Use the key as a unique identifier in a persistent email outbox, a database table of messages waiting to send. It includes the trial end so an extension produces a different identity. Before dispatch, recalculate the decision and require its key to match the queued one. A decision-stage job that arrives in the final window should be skipped, not delivered alongside the final message.
This function does not send, schedule, or deduplicate emails by itself. The worker still needs a unique database constraint, exclusive job claiming, recorded delivery outcomes, and a provider retry policy. Use our durable outbox guide for those mechanics. When you cannot refresh account state, defer the reminder rather than sending from a stale snapshot.
Normalize the snapshot on the server. "Paid" should mean the account really has the paid access your product grants, not that someone clicked a checkout button. Keep suppression reasons in logs so support can explain a missing reminder without reading every event. Avoid logging message bodies or secret links.
Build a plain-text draft from verified fields
The second example uses the decision above and an authenticated billing-page URL. Use it for optional upgrade reminders. Validate database and API inputs before calling it. If you render the same fields into React Email, use ordinary text children rather than inserting them as HTML.
import type { ReminderDecision } from "./trial-reminder-decision";
interface TrialCopy {
productName: string;
accessAfterExpiry: string;
upgradeTerms: string; // Approved pricing/checkout explanation
billingPageUrl: string;
appOrigin: string;
}
export function buildTrialReminder(
decision: ReminderDecision,
copy: TrialCopy,
): { subject: string; text: string } {
const url = new URL(copy.billingPageUrl);
const origin = new URL(copy.appOrigin);
if (
url.protocol !== "https:" || url.origin !== origin.origin ||
url.username || url.password || url.search || url.hash
) throw new Error("Use a trusted HTTPS billing page without tokens");
const product = copy.productName.trim();
if (!product || /[\r\n]/.test(product)) {
throw new Error("Invalid product name");
}
if (!copy.accessAfterExpiry.trim() || !copy.upgradeTerms.trim()) {
throw new Error("Missing approved access or upgrade policy");
}
const date = new Date(decision.trialEnd * 1000);
if (!Number.isFinite(date.getTime())) {
throw new Error("Invalid trial expiry");
}
const deadline = new Intl.DateTimeFormat("en-GB", {
dateStyle: "long", timeStyle: "short", timeZone: "UTC",
}).format(date) + " UTC";
const prefix = decision.stage === "final" ? "Reminder: " : "";
return {
subject: prefix + "Your " + product + " trial ends on " + deadline,
text: [
"Your " + product + " trial ends on " + deadline + ".",
copy.accessAfterExpiry.trim(),
copy.upgradeTerms.trim(),
"Choose a plan: " + url.href,
"Questions about continuing? Reply to this email.",
].join("\n\n"),
};
}Set the billing page to a stable route such as https://app.example.com/settings/billing. After sign-in, authorize workspace access on the server before creating a billing-portal session. Opening the email link should display a page; it should never charge a card or cancel a plan.
This returns the message body, not the full sending integration. Configure a monitored reply address and add the preference or unsubscribe controls your promotional flow requires. A founder signature is unhelpful if replies disappear into an unattended inbox. The approved copy fields must describe the same policy shown in-app.
Test account changes before testing subject lines
We ran the two functions above against local fixtures covering the 72-hour and 24-hour boundaries, expiry, upgrades, cancellation, extensions, promotional opt-out, delivery suppression, and unsafe URLs. Those checks validate the example logic. They do not prove delivery through your provider or validate your app's billing integration.
- Upgrade between enqueue and dispatch: the queued reminder is skipped.
- Cancel while still trialing: no optional upgrade reminder follows.
- Extend the trial: the old key no longer matches, and the new deadline drives scheduling.
- Run the worker twice: only one outbox row exists for the same stage and expiry.
- Read on mobile: the deadline, consequence, and button are visible without a product tour.
- Follow the link while signed out: sign-in returns the authorized owner to billing.
For the provider side, use Stripe simulations and test clocks to advance a sandbox subscription through the trial. Your app's scheduler must use the simulated time separately; advancing a Stripe clock does not advance your server's wall clock. Stripe says it does not send its hosted trial reminders in sandboxes, so their absence there is not evidence of a broken live notice setting.
Measure completed decisions
Count eligible trial workspaces, delivered reminders, and paid conversions within a fixed observation window. For example, report paid conversions within seven days of the original scheduled expiry, with that definition written next to the metric. Track extensions separately so an extension does not silently move someone between cohorts.
Split the results by automatic billing versus optional upgrade, and by whether the workspace completed your activation event. Successful billing of a card collected at signup is a different decision from choosing a paid plan after a no-card trial. Combining them hides what the emails actually need to improve.
- Paid conversions for a defined group of eligible trial workspaces
- Replies naming a setup problem, pricing objection, or missing feature
- Billing questions and cancellations after a notice
- Open rate treated as proof of a purchase decision
- Conversions counted without allowing the whole observation window
- A small subject-line test reported as a universal conversion lift
A startup with few trials should read the replies before running elaborate experiments. If several users cannot finish the same integration, improve that setup step. If they ask whether files will disappear, clarify the access policy in both the email and product. Discounts will not explain a confusing deadline.
Ship the correct decision email first.
- Verify the expiry, access policy, billing behavior, and recipient.
- Use separate copy for optional upgrades and automatic billing.
- Refresh account state before dispatch and suppress stale jobs.
- Give the customer one working action and a monitored reply path.
- Review paid conversions and real objections before adding more sends.