Skip to content

abandoned-checkout

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.

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

Trigger { event: "checkout_started" }
Tags demo, checkout
Consent purpose emailMarketing

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.

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.
*
* Example: add it with `cow add example abandoned-checkout`; rename the
* events and the email to your domain's vocabulary.
*/
export default defineJourney({
trigger: { event: "checkout_started" },
purpose: "emailMarketing",
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.email.send({
template: "abandoned-checkout",
props: {
checkoutUrl: typeof checkoutUrl === "string" ? checkoutUrl : undefined,
firstName:
typeof me.traits.firstName === "string"
? me.traits.firstName
: undefined,
},
});
},
});

The example ships this scenario, copied in beside the journey. cow test replays it 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
# copy it into your project
cow add example abandoned-checkout
# run its scenario on a virtual clock
cow test abandoned-checkout --scenario scenarios/abandoned-checkout.timeout.json