Skip to content

abandoned-checkout

One journey that opens a recovery window when a checkout starts, and closes it quietly when the order lands.

Journey What it does
abandoned-checkout Reminds someone who left items in their cart, and stops if they buy.

These are names your own app sends through track, not names Cowliss defines. Rename them in the copied files to whatever your app already sends.

Event Named by
checkout_started abandoned-checkout
purchase_completed abandoned-checkout
Terminal window
cow init --blueprint abandoned-checkout

Every file above lands in your repo, yours to edit. In a repo that already has a cow.json, add it with cow add blueprint abandoned-checkout instead.

Recovery window with a success mark. checkout_started opens a 24-hour window; a purchase_completed inside it closes the window quietly and marks the cart cart_recovered, a trait your segments and other journeys can read (“recovered once” vs “never”). Silence spends exactly one reminder email and closes.

Abandoned carts, incomplete onboarding checklists, unfinished KYC: any funnel step with a clear completion event and a deadline.

Rename the events and the email to your domain. To escalate instead of sending once, loop with a second api.waitForEvent and a stronger second email before closing.

Abandoned-checkout recovery: checkout_started opens a 24-hour window. A purchase_completed inside it closes the window quietly and marks the cart recovered, a trait your segments and other journeys can read. Silence spends exactly one reminder email and closes.

marketing and no enrollment, so one shopper goes through this once, ever: their second abandoned cart is not nagged about. Add enrollment: { cooldown: "30d" } to reach a repeat abandoner again, a month after their last reminder.

Blueprint: add it with cow add blueprint abandoned-checkout; rename the events and the email to your domain’s vocabulary.

import { defineJourney } from "@cowliss/cli/journeys";
/**
* Abandoned-checkout recovery: `checkout_started` opens a 24-hour window. A
* `purchase_completed` inside it closes the window quietly and marks the cart
* recovered, a trait your segments and other journeys can read. Silence
* spends exactly one reminder email and closes.
*
* `marketing` and no `enrollment`, so one shopper goes through this once,
* ever: their second abandoned cart is not nagged about. Add
* `enrollment: { cooldown: "30d" }` to reach a repeat abandoner again, a
* month after their last reminder.
*
* Blueprint: add it with `cow add blueprint abandoned-checkout`; rename the
* events and the email to your domain's vocabulary.
*/
export default defineJourney({
trigger: { event: "checkout_started" },
purpose: "marketing",
from: "Acme <[email protected]>",
description:
"Reminds someone who left items in their cart, and stops if they buy.",
tags: ["demo", "checkout"],
run: async (event, api) => {
const purchased = await api.waitForEvent("purchase_completed", {
timeout: "24h",
});
if (purchased) {
await api.traits.set("cart_recovered", true);
return;
}
// The checkout link belongs to your store, so the trigger event carries
// it; the email drops its button when the prop is missing.
const checkoutUrl = event.properties.checkoutUrl;
const me = await api.profile.get();
await api.send.email({
template: "abandoned-checkout",
props: {
checkoutUrl: typeof checkoutUrl === "string" ? checkoutUrl : undefined,
firstName:
typeof me.traits.firstName === "string"
? me.traits.firstName
: undefined,
},
});
},
});

cow test replays this on a virtual clock: the trigger fires at the start, events arrive at their offsets, and expect is the ordered list of capability calls the run must make, each input matched as a subset.

abandoned-checkout.timeout.json
{
"user": {
"id": "usr_1",
"traits": { "email": "[email protected]", "firstName": "Ada" }
},
"events": [],
"expect": [
{
"activity": "sendEmail",
"input": {
"template": "abandoned-checkout",
"props": { "firstName": "Ada" }
}
}
]
}
Terminal window
cow test abandoned-checkout --scenario scenarios/abandoned-checkout.timeout.json