For the complete documentation index, see llms.txt. This page is also available as Markdown.

Abandoned Basket Email Integration

This guide explains how to build an automated abandoned basket email campaign using the Limio API and a third-party email service provider. We use Resend as an example, but the same principles apply to any transactional email provider such as SendGrid, Mailgun, or Amazon SES.

Prerequisites. This guide assumes you are familiar with the Abandoned Basket API and have already set up OAuth authentication. If you are looking to manually inspect abandoned baskets, see Guide: Inspect and Follow Up on Abandoned Baskets.

Overview

The high-level flow for an abandoned basket email integration is:

  1. Authenticate with the Limio API using OAuth client credentials

  2. Fetch abandoned baskets using the GET /api/checkout/abandoned endpoint

  3. Filter baskets to identify those eligible for email outreach

  4. Build a recovery email with a link back to the customer's checkout

  5. Send the email via your provider

  6. Track which baskets have already been emailed to avoid duplicate sends

Fetching Abandoned Baskets

Use the createdAfter parameter to control the lookback window. For example, to fetch baskets abandoned in the last 24 hours:

GET https://{tenant}/api/checkout/abandoned?limit=50&createdAfter=2025-01-01T00:00:00Z

Each basket in the response includes customerDetails, orderItems, a completed flag, and a recoveryLink. Only baskets where completed is false are considered abandoned.

If more results exist than the limit allows, the response includes a queryMore cursor. Pass it as a query parameter on the next request to page through results.

Key Considerations

Integration strategy: polling vs event-driven

There are two main approaches to detecting incomplete baskets and triggering emails. You can use one or combine both.

A scheduled job — a cron task, a Lambda on a CloudWatch timer, or a simple script on a schedule — periodically calls GET /api/checkout/abandoned with a createdAfter filter and processes any new baskets.

Why this approach works well:

  • Full control over timing. You decide when to send: after 1 hour for urgency, or after 24 hours for a traditional campaign.

  • Simple to operate. No always-on infrastructure is required beyond a scheduler. A cron job or a serverless function on a timer is sufficient.

  • Batch-friendly. You can process multiple baskets per run, apply deduplication, and rate-limit sends to stay within your email provider's limits.

Approach 2: Event-driven (webhook + delayed check)

Limio fires a checkout.initiated webhook when a customer starts a checkout. You can listen for this event, wait a defined period, and then check whether the basket was completed.

Why you might consider this approach:

  • Near real-time awareness. You know about the basket the moment it is created, which can reduce the time between checkout inactivity and email delivery.

  • Targeted checks. Instead of scanning all baskets on each poll, you only check specific baskets that were flagged by a webhook.

Limitations to be aware of:

  • The webhook fires at checkout start, not when the basket becomes inactive. You still need to wait and re-check the Abandoned Basket API to confirm the basket was not completed.

  • The webhook payload does not include customerDetails.email. The email is only captured later when the customer fills in the checkout form (or logs in via SSO). You would use the basket ID from the webhook as your reference, then call the API after a delay to retrieve the email and other details.

  • More complex infrastructure. You need an always-on endpoint to receive webhooks, plus a queue or delay mechanism (e.g. SQS with a delay, a scheduled retry) to check back later.

Which approach should I use? For most use cases, polling is the simplest and most reliable approach. The event-driven approach adds complexity without eliminating the need to call the API — since the webhook fires at checkout initiation and does not include the customer's email. Consider webhooks as an optimisation if you need to reduce the time between checkout inactivity and email delivery.

Only baskets with an email can be contacted

A basket is created as soon as a customer clicks a call-to-action on your shop — but customerDetails.email is only populated once the customer enters their email on the checkout form (or logs in via SSO). Filter out any baskets where customerDetails.email is missing before attempting to send.

Wait before sending

A basket is created the moment a customer clicks "Subscribe" or "Buy Now" — they may still be filling in the checkout form. If you send an email immediately, you risk contacting a customer who is still mid-checkout.

Introduce a minimum age threshold before considering a basket eligible. For example, only email baskets created at least 1–2 hours ago for a standard campaign, or 30–60 minutes for a more aggressive approach. Compare the basket's created timestamp to the current time and skip any baskets that are too recent.

There is no definitive moment when a basket becomes "abandoned". It is simply incomplete. You choose the threshold that makes sense for your business — there is no right answer. A shorter delay means faster outreach but risks emailing customers who are still deciding; a longer delay gives more confidence but reduces urgency.

Avoid duplicate emails and suppress completed orders

If you run your integration on a schedule (e.g. every 15 minutes via a cron job), you must track which baskets have already been emailed. Without deduplication, the same customer could receive multiple recovery emails for the same basket.

A simple approach is to maintain a persistent record of processed basket IDs — a JSON file, a database table, or a key-value store. Before sending, check whether the basket ID exists in your record. After a successful send, add it. Prune old entries periodically (e.g. remove records older than 72 hours) to keep the store manageable.

You should also check the basket's completed flag before sending. If there is a delay between fetching the basket and sending the email (e.g. you batch-fetch then process sequentially), consider calling the API again for individual baskets to confirm they are still incomplete. If you are using the event-driven approach, you can also listen for the order completion event and clear the basket from your queue to suppress the email before it is sent.

Validate the email address

Always validate the format of customerDetails.email before passing it to your email provider. Malformed addresses will cause API errors and may affect your sender reputation.

Filter by basket type

The Abandoned Basket API returns all incomplete baskets — including new subscriptions, upgrades, referral flows, reseller flows, and update subscription baskets. You will likely want to target only specific types.

Use the tracking.tag field to filter baskets by sales channel. The tag value is the first tag on the offer that started the basket, falling back to any tracking.tag value supplied when the checkout was initiated. It therefore reflects how the offer is tagged in your catalog, not the page the customer was on. Work with your team to identify which offer tags correspond to which channels, then filter accordingly. To filter on where the checkout actually started, use the basket's separate entry field, which holds the path (with query string) of the page the checkout was initiated from.

Typically, you will want to target baskets from direct and referral flows — these represent end customers purchasing for themselves. Reseller baskets (where a partner is standing up a subscription on behalf of a client) should usually be excluded, as the partner manages that relationship.

Each abandoned basket includes a recoveryLink field. This is a tokenised URL that takes the customer back to the exact checkout session they abandoned, with their basket pre-populated.

The full recovery link format is:

Include this link as the primary call-to-action in your email so the customer can return to their checkout in a single click.

Preserving UTM parameters

The basket's entry field contains the original URL the customer used to reach the checkout, including any UTM parameters. You have two options:

  • Preserve original UTMs — reappend the UTMs from entry to the recovery link, so conversions are attributed to the original campaign that brought the customer to checkout.

  • Use new UTMs — generate abandoned-cart-specific UTMs (e.g. utm_campaign=abandon_cart_recovery) so you can measure the recovery campaign separately.

Check with your marketing team which approach they prefer. You can also combine both by preserving the original utm_source while overriding utm_campaign.

To extract the original UTM parameters from the entry field:

The full recovery link is then constructed as:

Applying Promo Codes

To incentivise customers to complete their purchase, you can automatically apply a promo code by appending &pc=<code> to the recovery link:

When the customer clicks this link, the promo code COMEBACK10 is applied to their basket automatically. This is a powerful way to offer a time-limited discount in your recovery email without requiring the customer to enter a code manually.

Promo code requirements. The promo code must already be configured in Limio. If the code is expired, invalid, or not compatible with the items in the basket, the offer will still appear but the discount will not be applied. See How to Configure Promo Codes for setup instructions.

Enriching Emails with Offer Details

You can enhance your recovery emails by fetching additional offer details from the Limio API. Each basket's orderItems includes a path field (e.g. /offers2/Monthly Standard) that you can use to query the Offers API:

The response includes offer attributes such as display_price__limio and checkout_description__limio, which you can use to display the price and a short description in the email body. This gives the customer a clear reminder of what they left behind.

Example: Sending with Resend

Resend is a developer-friendly transactional email API. After creating an account and verifying your sending domain, you can send emails using their SDK:

The buildEmailHtml function is where you construct your email template. Include the offer name, price (if enriched), and a prominent call-to-action button pointing to the recovery link with an optional promo code appended.

Putting It All Together

A complete abandoned basket email integration follows this sequence:

  1. Authenticate — obtain a Bearer token using OAuth client_credentials grant

  2. Fetch baskets — call GET /api/checkout/abandoned with a createdAfter filter

  3. Filter — remove baskets that are too recent, missing an email address, or already emailed

  4. Enrich — optionally fetch offer details to include pricing and descriptions in the email

  5. Build the recovery link — use the recoveryLink from the basket, and append &pc=<code> if you want to include a promo code

  6. Send — deliver the email via your provider and confirm success

  7. Record — mark the basket ID as processed in your deduplication store

This flow can be triggered manually, run on a cron schedule, or integrated into a larger marketing automation pipeline.

Last updated

Was this helpful?