Marketing
Abandoned cart emails in Go
A Go package that records a basket somebody left idle, sends a sequence of reminder emails about it on a schedule the store sets, stops the moment the basket becomes an order, and credits that order to the record. It adds no third-party dependency: the emails go out through whatever email notifier the store installed.
- Marketing module
- 3 settings
- 40 tests
- 10 API operations
- ext/cart-recovery
What it does
The engine records abandonment and announces it, and deliberately stops there, because when and how often to write to a shopper about a stale basket is, in the package’s words, “a marketing decision with opt-out obligations attached”. This module is the other half of that decision: it owns the schedule, and the engine owns the channels.
Its clock is its own. The engine calls a cart abandoned when its TTL runs out, thirty days by default, which is the right clock for retention and the wrong one for a reminder. So the module keeps a record of each basket idle past a threshold the store sets in minutes: by default 10 minutes for a basket that reached checkout, where an email was typed, and 30 for one that did not. The engine’s TTL is untouched.
Each record runs the sequence for its kind. By default a checkout gets three emails, an hour, then 20 hours, then 48 hours apart, and a cart two, after two hours and then 24. Every rule is checked again at the moment of sending, against the basket as it is then. The figures keep apart the orders a reminder preceded and the shoppers who came back on their own.
Configuration
Nothing is required. The storefront address can come from Config, which the reference binary fills from STOREFRONT_URL, and everything can be changed on the Marketing › Automations screen, which wins where both say something.
| Setting | Environment variable | Required | What it does |
|---|---|---|---|
StorefrontURLStorefront address | STOREFRONT_URL | No | Where the shopper’s basket lives, as an absolute base URL such as https://shop.example.com; the link lands on that plus /cart/<token>. Unset in both places, a reminder carries the cart token and no link, and a bad value stops the module at startup. |
checkoutAbandoned checkout recovery | — | No | The sequence for a basket with an email on it: on or off, the minutes of inactivity before it counts (1 to a week), and up to five steps, each a wait of up to 30 days and an email. Screen only; there is no Config field for it. |
cartAbandoned cart recovery | — | No | The same, for a basket that never reached checkout. It can be reached only through a signed-in shopper’s verified address. Screen only. |
The module reads its Config struct, not the environment. The variable names are the ones the package’s own example or the reference binary uses; in your own main() you choose where each value comes from. Where a setting has a panel label, the admin’s settings drawer can hold it too, and a value typed there wins over Config.
Setting it up
- Set up emailInstall an email notifier and set it up under Notifications › Setup Email. Without one that delivers, every reminder is held as no_email_provider rather than written into a log.
- Install the modulePass cartrecovery.New to gocommerce.New, as below, or start the reference binary with -recovery and STOREFRONT_URL set.
- Check the automationsUnder Marketing › Automations, review the two sequences and the storefront address. The three reminder emails are worded on Notifications › Setup Email, as Checkout recovery #1, #2 and #3.
- Watch the basketsOrders › Abandoned checkouts lists each record with its timeline; Marketing › Cart recovery shows the funnel and the recovered revenue.
main.go
import (
"os"
"github.com/itswadesh/gocommerce/core"
cartrecovery "github.com/itswadesh/gocommerce/ext/cart-recovery"
)
app, err := gocommerce.New(cfg,
cartrecovery.New(cartrecovery.Config{
StorefrontURL: os.Getenv("STOREFRONT_URL"),
}),
) The package doc has no example of its own; this is how the reference binary’s -recovery flag installs it. cfg is your gocommerce.Config. Import path github.com/itswadesh/gocommerce/ext/cart-recovery.
How it works
-
Through the store’s own email
A reminder is a notification sent with the engine’s App.Notify on the email channel, through whichever email notifier the store installed, in the wording the store edits. The module ships no transport of its own.
-
A counted link
When the engine knows its panel address, a reminder’s link goes through GET /x/cart-recovery/r/{token}, which counts the click and redirects to the basket. Without one, the link goes straight to the storefront’s /cart/<token> and no click is counted; the report then says “Not counted”.
-
The store’s own brand
On an install with several brands, the counted link goes through the panel address of the store’s own brand and names its store, so a shopper only ever sees the brand of the shop they visited, and the click finds the right basket.
-
Checked at the moment of sending
Before each email the basket is read again. A basket that was bought, emptied, deleted or has nothing in stock is not written about, and a shopper who is in the basket right now is not written to until the visit goes quiet.
-
Credited to the order
order.created names the basket it came from, so the record becomes recovered with the order’s number and total, and notes whether a message had gone out first. A shopper who came back unprompted is recovered all the same, and not counted for the sequence.
-
Safe across instances
The sender claims up to 100 due steps at a time with FOR UPDATE SKIP LOCKED and holds them for five minutes, and the pass runs once a minute per store, plus once at startup.
What it does not do
Read these before an order depends on it. No store is known to run GoCommerce in production yet, so these come from the code, not from anyone’s experience.
- Email onlyA basket carries an address and no phone number, so SMS and WhatsApp steps are not available. Creating a discount and notifying staff are listed on the screen as not built yet.
- Nothing before installA basket that went idle before the module was installed is recorded and shown, and never written to.
- Three failures stop a stepA send that fails is tried again after 10 and then 20 minutes. After the third failure the sequence stops with send_failed until the automation screen is saved again.
- No unsubscribe of its ownThe module has no opt-out list. An operator stops the reminders for one basket by suppressing it, with a reason such as requested_no_contact.
- The link opens the basketA recovery link is as much a credential for the basket as its cart token. Copying it from the admin is a POST, and recorded; the notification log shows it as hidden.
- Figures in the store’s currencyA basket left before the store changed its currency still counts as a basket, but its money is not added to the store’s figures.
FAQ
Questions about the Cart recovery module
When does a basket count as abandoned?
When it has sat untouched for the threshold its kind has: 10 minutes by default for a basket with an email on it, 30 for one without. Each can be set from 1 minute to a week on the automation screen, and the module checks once a minute.
Which email service does it use?
Whichever the store installed as its email notifier. The module hands the engine a notification and the engine delivers it; with no notifier that delivers, nothing is sent and each record says it is waiting for a provider.
Can I change the wording?
Yes. The three reminders are templates on Notifications › Setup Email, Checkout recovery #1, #2 and #3, with the basket’s summary, item count, subtotal, discount code and recovery_url to use. A step names one, and two steps may name the same.
Can staff send a reminder by hand?
Yes, from the basket’s page, with abandonment.contact. A manual send is recorded on the timeline and leaves the sequence where it was. Changing the automation needs abandonment.automate, which managers have by default and staff do not.
Does it text abandoned baskets?
No. A basket carries no phone number, so every step is an email, and the engine’s own SMS notifiers write about order events only.
Source
Everything on this page is read from ext/cart-recovery in the GoCommerce repository, MIT licensed. When this page and the code disagree, the code is right and this page is out of date.
Try it against a store of your own
The backend + admin quick start runs GoCommerce’s API and admin on your own machine, without a storefront. Add this module to it and try it on test orders before a real one depends on it.