# Billing and plans Source: https://help.retainful.com/account/billing-and-plans Plans, email credits, usage limits, invoices, and how billing works on Shopify vs. direct. ## Plans Retainful has a **Free** plan to get started and a **Pro** plan for growing stores. Plans differ mainly in monthly email volume and feature access. See your current plan and what's included under **Settings → Billing → Overview**. ## How usage works Your plan includes a **monthly email allowance**. Every campaign and automation email counts toward it; the usage indicator in the app shows where you stand this month. * Approaching the limit? Retainful warns you in advance. * Need more in a heavy month? **Additional email credits** can top you up without changing plans. * Usage resets at the start of each billing cycle. Track the details under **Settings → Billing → Account Usage**. ## Payment methods Subscriptions are billed by card through Stripe. Upgrade from **Settings → Billing → Overview** — you'll go through a secure checkout, and your plan activates immediately. Invoices appear under **Payment History**. If you installed Retainful from the Shopify App Store, your subscription can be billed by Shopify and appears on your regular Shopify invoice. Plan changes are approved inside Shopify admin. ## Invoices Every charge — subscription and credit top-ups — is listed under **Settings → Billing → Payment History** with downloadable invoices for your accounting. ## Cancel or downgrade You can cancel your subscription from the billing page. Your paid features remain active until the end of the period you've paid for, after which the organization moves to the Free plan. You can reactivate a cancelled subscription any time before the period ends. Cancelling your subscription doesn't delete your data — your contacts, automations, and history stay intact on the Free plan (within its limits). # Account security Source: https://help.retainful.com/account/security Protect your account with two-factor authentication and good session hygiene. Your Retainful account can email your entire customer base — treat its security accordingly. ## Two-factor authentication (2FA) With 2FA enabled, signing in requires your password **plus** a 6-digit code from an authenticator app. Even a leaked password isn't enough to get in. Go to **Settings → Account → Security** and click **Enable two-factor authentication**. Use any authenticator app — Google Authenticator, 1Password, Authy — to scan the code, then enter the 6-digit code to confirm. Store the recovery codes somewhere safe (a password manager). They're your way in if you lose your phone. Make 2FA mandatory practice for every [team member](/account/team-and-roles) with access to campaigns or billing — an account is only as secure as its least-protected admin. ## Passwords and sessions * Change your password from **Settings → Account → Security**. Use a unique password from a password manager. * Forgot it? Use **Forgot password** on the login page for an email reset link. * When someone leaves your team, **remove them from the organization** — don't share logins in the first place; individual accounts cost nothing and preserve accountability. ## Account email verification Your login email must be verified before you can send. If you change it, you'll verify the new address the same way. # Team and roles Source: https://help.retainful.com/account/team-and-roles Invite teammates, assign roles, and control who can do what in your organization. Retainful organizations support multiple team members with role-based permissions — your designer can edit templates without being able to touch billing. ## Invite a team member Go to **Settings → Account → Users** and click **Invite**. Enter their email and pick a role. The invitee gets an email link. If they don't have a Retainful login yet, they create one during acceptance. From the same page you can change a member's role or remove them. Pending invitations can be revoked before they're accepted. ## Roles and permissions Roles bundle permissions — like `content:edit`, which gates editing campaigns, forms, and templates. Use the built-in roles, or create **custom roles** with exactly the permissions a job needs: 1. Go to **Settings → Account → Users → Roles**. 2. Click **Create role**, name it, and select its permissions. 3. Assign it to members. Members without a given permission see the relevant pages as read-only or restricted. ## Multiple organizations An organization is a workspace — one store (or brand) with its own audience, campaigns, billing, and team. If you run several stores: * Create an organization per store and switch between them from the organization menu. * Team membership is **per organization** — invite people only to the workspaces they work in. * Billing is also per organization; each has its own plan. Agencies: invite your client as the owner of their organization and keep your team as members — handovers later become trivial. # Manage contacts Source: https://help.retainful.com/audience/contacts View, add, edit, and organize the people in your audience. **Audience → Contacts** is your full contact directory. Search by name or email, filter by list or segment, and click any contact to open their profile. ## The contact profile Each contact's page brings together everything Retainful knows about them: * **Profile details** — name, email, phone, location, and any [custom fields](/audience/custom-fields). * **Subscription status** — whether they can receive marketing email (and WhatsApp, if connected). * **Activity timeline** — a chronological feed of everything that happened: emails opened and clicked, lists joined, forms submitted, orders placed, and custom events from your integrations. * **Engagement analytics** — how they interact with your emails over time. The activity timeline is the fastest way to answer "why did this customer get that email?" — it shows which automation or campaign sent each message. ## Add a contact manually 1. Go to **Audience → Contacts** and click **Add contact**. 2. Enter at least an email address; name, phone, and other fields are optional. 3. Choose their subscription status and any lists they should join. Only mark a contact as **Subscribed** if they actually gave you permission to email them. For more than a handful of contacts, use the [import wizard](/audience/import-contacts) instead. ## Edit or delete a contact Open the contact and click **Edit** to change their details, or **Delete** to remove them entirely. Deleting a contact removes their profile and history. If someone asks to be removed under privacy laws (GDPR, CCPA), deleting their contact honors that request. ## Bulk actions Select multiple contacts in the directory to act on them together: * **Subscribe / Unsubscribe** — change marketing status in bulk. * **Add to / remove from list** — reorganize group membership. * **Send double opt-in** — ask contacts to confirm their subscription by email. * **Delete** — remove many contacts at once. Large bulk actions run in the background — you'll see progress and can keep working. ## Single vs. double opt-in * **Single opt-in**: a contact who submits your form is subscribed immediately. * **Double opt-in**: the contact receives a confirmation email and must click it before they're marked subscribed. Slower list growth, but higher quality and required by law in some countries. You can configure your opt-in preference under **Settings → Email → Opt-in**. # Custom fields Source: https://help.retainful.com/audience/custom-fields Store extra information on every contact and use it for personalization and segmentation. Custom fields let you store information Retainful doesn't track by default — anything from a customer's shoe size to their loyalty tier. ## Create a custom field 1. Go to **Settings → Custom Fields**. 2. Click **Add field**, name it, and choose its type: | Type | Use for | | ------------ | ------------------------------------------ | | **Text** | Free-form values like "favorite color". | | **Number** | Quantities like loyalty points. | | **Date** | Birthdays, renewal dates. | | **Boolean** | Yes/no flags like "wholesale customer". | | **Dropdown** | A fixed set of options like T-shirt sizes. | ## How fields get filled * **Imports** — map CSV columns to custom fields in the [import wizard](/audience/import-contacts). * **Signup forms** — add a question to your form and store the answer in a field. * **Manually** — edit any contact's profile. * **API** — send `custom_properties` when [creating contacts](/developers/api/contacts) from your own systems. * **Store sync** — some platform data (like tags) arrives as properties automatically. ## Using custom fields ### Personalization Insert any field into your emails with merge tags — "Hi Jane, your **Gold** membership renews soon." See [Personalization](/campaigns/personalization). ### Segmentation Filter on custom fields in the [segment builder](/audience/segments) — for example, everyone whose "loyalty\_tier" is "Gold" and who hasn't ordered this quarter. ### Automation branching Use a field in a **Binary** step to send different emails to different groups inside one automation. Keep field names consistent and lowercase (like `loyalty_tier`, not "Loyalty Tier!") — it keeps imports, API calls, and segments tidy as your usage grows. # Export contacts Source: https://help.retainful.com/audience/export-contacts Download your contacts as a CSV file. Your data is yours. Export any part of your audience as a CSV file whenever you need it — for analysis, backups, or use in another tool. ## Create an export Go to **Audience → Exports** and click **Create export**. Export everyone, a specific [list](/audience/lists), or a [segment](/audience/segments). Exports run in the background. When yours is ready, a download link appears on the Exports page. Download links expire after a period for security. If a link has expired, just run the export again. ## What's included The CSV contains each contact's profile fields (email, name, phone, location), subscription status, and custom fields — ready to open in Excel or Google Sheets. ## Common uses * **Backups** — keep a periodic copy of your audience. * **Analysis** — slice your audience in a spreadsheet or BI tool. * **Suppression elsewhere** — export unsubscribed contacts to suppress them in other tools you use. If you need contact data flowing into your own systems continuously rather than as one-off files, use the [REST API](/developers/api/contacts) instead. # Import contacts Source: https://help.retainful.com/audience/import-contacts Bring your subscribers into Retainful with the CSV import wizard. Moving from another email platform? Export your contacts there as a CSV file and bring them into Retainful with the import wizard. ## Prepare your file * Format: **CSV** with a header row (the first row names each column). * Required column: **email**. Everything else — first name, last name, phone, custom fields — is optional. * One contact per row. A minimal file looks like this: ```csv contacts.csv theme={null} email,first_name,last_name jane@example.com,Jane,Smith raj@example.com,Raj,Patel ``` Only import people who gave you permission to email them. Purchased or scraped lists cause spam complaints, damage your sender reputation, and can violate anti-spam laws like CAN-SPAM and GDPR. ## Run the import Go to **Audience → Imports** and click **Import contacts**, then upload your CSV. Match each column in your file to a Retainful contact field. Unrecognized columns can be mapped to [custom fields](/audience/custom-fields) so no data is lost. Pick which [list](/audience/lists) the imported contacts should join. Creating a dedicated list per import (like "Mailchimp import – June 2026") makes cleanup easy later. Tell Retainful whether these contacts are subscribed. If they were confirmed subscribers in your previous tool, import them as **Subscribed**. If you're unsure, import as non-subscribed and run a re-permission campaign. The import runs in the background. **Audience → Imports** shows progress and a summary when it finishes — how many contacts were created, updated, or skipped. ## How duplicates are handled Contacts are matched by email address. If an imported email already exists in Retainful, the existing contact is **updated** with the new information rather than duplicated. ## Common issues Rows with missing or invalid email addresses are skipped. Check the import summary for the count, fix your file, and re-import — already-imported contacts are simply updated. This is a column-mapping issue. Re-run the import and double-check the mapping step — the wizard previews your data so you can verify before confirming. Import them too — but as **Unsubscribed**. That preserves their opt-out so they never accidentally receive marketing from you again. # Lists Source: https://help.retainful.com/audience/lists Create static groups of contacts for organizing and targeting your audience. A list is a named group of contacts. Unlike [segments](/audience/segments), lists don't change on their own — contacts join when you (or they) add them, and leave when removed. ## When to use a list Lists work best for things people **opt into**: * Newsletter subscribers * Customers who asked for restock alerts * Attendees of an event or promotion * Subscribers imported from your previous email platform ## Create a list 1. Go to **Audience → Lists** and click **Create list**. 2. Give it a clear name and an optional description. 3. Add contacts — from the contact directory, an import, a signup form, or an automation. ## How contacts get into lists | Source | How it works | | ---------------- | --------------------------------------------------------------------------------------------------- | | **Signup forms** | Each form can add new subscribers to a list of your choice. | | **Imports** | The import wizard asks which list the file's contacts should join. | | **Manual** | Select contacts in the directory and use **Add to list**. | | **Automations** | The **List Update** step adds or removes contacts as part of a flow. | | **API** | Your own systems can manage list membership through the [REST API](/developers/api/contact-groups). | ## List analytics Open any list to see: * **Membership stats** — how many contacts are in the list and their subscription breakdown. * **Growth** — how the list has grown (or shrunk) over time. * **Engagement** — how actively the list's members open and click your emails, including an engagement distribution so you can spot how much of the list is highly engaged versus dormant. A shrinking open rate on a list usually means it's aging — consider a re-engagement campaign for inactive members, and remove contacts who never respond. Smaller, engaged lists outperform big, stale ones. ## Merge and clean up * **Merge** two lists into one when you've ended up with duplicates (for example, after several imports). * **Delete** lists you no longer need — deleting a list never deletes the contacts in it. # Audience overview Source: https://help.retainful.com/audience/overview How contacts, lists, and segments work together in Retainful. Your audience is everyone Retainful knows about — customers synced from your store, subscribers from your signup forms, and contacts you've imported. Three concepts organize them: ## Contacts, lists, and segments | Concept | What it is | Example | | ----------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------- | | **Contact** | One person, identified by their email address (and optionally phone). | `jane@example.com` with her order history and profile. | | **List** | A static group you add contacts to and remove them from manually. | "Newsletter subscribers", "VIP customers". | | **Segment** | A dynamic group defined by rules. Membership updates automatically as contacts change. | "Ordered in the last 30 days", "Opened but didn't click". | Use **lists** for things people opt into, and **segments** for behavior-based targeting. A contact can be in any number of lists and segments at once. ## Subscription status Every contact has an email subscription status that controls what you can send them: * **Subscribed** — opted in to marketing. They receive campaigns and marketing automations. * **Non-subscribed** — known to you (for example, a customer) but never opted in to marketing. They can still receive transactional-style messages where applicable. * **Unsubscribed** — opted out. Retainful automatically excludes them from marketing sends. Retainful enforces this for you — you can't accidentally email someone who unsubscribed. ## Where contacts come from * **Store sync** — customers from Shopify or WooCommerce, kept up to date automatically. * **Signup forms** — subscribers captured by your popups and embedded forms. * **Imports** — CSV files from your previous email tool. * **API** — contacts created by your own systems through the [REST API](/developers/api/contacts). ## What's in this section View profiles, activity timelines, and engagement history. Create and manage static groups of contacts. Build rule-based groups that update themselves. Bring subscribers from another tool with the CSV wizard. Download your contacts whenever you need them. Store extra information on every contact. # Segments Source: https://help.retainful.com/audience/segments Build dynamic, rule-based groups that update automatically as your customers' behavior changes. A segment is a saved set of rules. Any contact who matches the rules is in the segment; the moment they stop matching, they're out. You never manage membership by hand. ## Why segments matter Sending the same email to everyone is the fastest way to get ignored. Segments let you match the message to the person: * A VIP discount to your **highest spenders**. * A win-back offer to customers who **haven't ordered in 90 days**. * A different subject line for people who **opened but didn't click** your last campaign. ## Create a segment Go to **Audience → Segments** and click **Create segment**, or browse **Templates** for ready-made segments like recent buyers or lapsed customers. Each condition checks one thing about a contact — a profile property, a behavior, or an event. Combine conditions with **AND** (all must match) and **OR** (any can match). The builder shows how many contacts currently match. Save the segment and it's immediately available as an audience for campaigns and automations. ## What you can filter on | Type | Examples | | ---------------------- | ------------------------------------------------------------------------------------------------ | | **Profile properties** | Country, city, signup date, custom fields like "size preference". | | **Email behavior** | Opened or clicked a specific campaign, hasn't opened anything in 60 days. | | **List membership** | Is (or isn't) in a particular list. | | **Shopping behavior** | Number of orders, total spent, products purchased, last order date. | | **Events** | Any event Retainful has tracked for the contact, including custom events from your integrations. | ## Example: lapsed VIP customers A segment worth trying on most stores: * Total spent **is greater than** \$200, **AND** * Last order date **is more than** 90 days ago, **AND** * Email subscription status **is** Subscribed Pair it with a win-back automation that offers a [unique coupon](/automations/coupons). ## Using segments * **Campaigns** — choose the segment as your audience when sending. * **Automations** — use segment membership in entry rules or in a **Binary** (if/else) step to branch a flow. * **Analysis** — open a segment any time to see who currently matches. Segment membership is evaluated continuously — a contact who places an order this morning can drop out of your "hasn't ordered in 90 days" segment this afternoon. That's the point: your targeting is always current. # Suppressed contacts Source: https://help.retainful.com/audience/suppressed-contacts Why some contacts never receive your emails, and how suppression protects your sender reputation. Suppressed contacts are people Retainful will **not** email, even if they appear in your audience for a campaign. Suppression is automatic and protects your sender reputation. ## Why contacts get suppressed | Reason | What happened | | ------------------ | --------------------------------------------------------------- | | **Unsubscribed** | The contact clicked the unsubscribe link in one of your emails. | | **Hard bounce** | The email address doesn't exist or permanently rejects mail. | | **Spam complaint** | The contact marked your email as spam in their inbox. | Sending to these addresses again would hurt your deliverability for *everyone* — mailbox providers like Gmail watch how often your emails bounce or get flagged, and punish senders who keep trying. ## View suppressed contacts Go to **Audience → Suppressed contacts** to see who is suppressed and why, along with summary stats. ## Removing a suppression If a contact was suppressed by mistake — say, their mailbox was temporarily full and caused a bounce, or they unsubscribed accidentally and asked to come back — you can remove them from the suppressed list using the bulk **Remove from suppressed** action. Only un-suppress a contact when they have explicitly asked to receive your emails again. Un-suppressing spam complainers is never a good idea — if they complain twice, mailbox providers take it very seriously. ## Suppression vs. unsubscribed status They overlap but aren't identical: unsubscribing changes the contact's **subscription status** (their choice), while suppression is Retainful's **safety net** that also covers bounces and complaints. Either one is enough to stop marketing email to that address. ## Keeping your list healthy * Use **double opt-in** for signup forms to keep fake addresses out. * Don't email very old lists without a re-permission pass — dormant addresses turn into spam traps. * Watch your bounce rate in [campaign analytics](/campaigns/analytics); a spike usually means a stale audience. * See [Deliverability best practices](/email-setup/deliverability) for the full picture. # Abandoned cart recovery Source: https://help.retainful.com/automations/abandoned-cart-recovery Win back the ~70% of shoppers who start checkout and leave — the highest-ROI automation in ecommerce. Roughly seven out of ten online checkouts are abandoned. Cart recovery emails are how you win a meaningful share of them back — they reach someone who already chose your products, minutes after they almost bought. No other email you send will have a higher conversion rate. ## How Retainful recovers carts Your store tells Retainful the moment a shopper begins checkout — including their email (when entered) and the cart contents. If no order follows, the shopper enters your recovery flow after the first delay you set. Each email includes a **recovery link** that restores the cart exactly as the shopper left it — one click and they're back at checkout. Optionally, a [unique coupon](/automations/coupons) sweetens the deal. When the shopper completes their order, the exit rule you configured drops them out — so nobody gets a reminder about a cart they already bought. This does not happen on its own: you have to set the rule up. See [Required rule settings](#required-rule-settings). Completed orders from recovery links and coupons are credited to the automation, so you see exactly what it earns. ## Set it up 1. Make sure your store is connected ([Shopify](/integrations/shopify) or [WooCommerce](/integrations/woocommerce)). 2. Go to **Automations → Templates** and choose **Abandoned Cart Recovery**. 3. Review the pre-built emails — add your logo, adjust the wording, decide where (or whether) to offer a discount. 4. Set the [rule settings below](#required-rule-settings) — the flow is not safe to publish without them. 5. Click **Publish**. ## Required rule settings Two settings decide whether this flow makes you money or embarrasses you. Neither is configured for you. Set both before you publish. With no exit rule, the flow keeps emailing shoppers who have already paid. "You forgot something!" landing an hour after someone bought is the fastest unsubscribe you will ever earn. ### Exit rule Set the exit condition to match **any** of the following — an **OR** rule. The contact leaves the flow the moment either becomes true: | Condition | Window | | --------------------------------- | --------------------------- | | **Order paid** at least once | Since the start of the flow | | **Order fulfilled** at least once | Since the start of the flow | Match on either event rather than just one. Depending on the store and payment method, an order may register as fulfilled without a distinct paid event, or the reverse — an OR rule catches the shopper who bought either way. Scope both conditions to **since the start of the flow**. An unscoped "has ever paid" condition exits every returning customer the instant they enter — which is precisely the audience worth recovering. ### Re-entry Set [re-entry](/automations/triggers#re-entry-rules) to a **1 day cooldown**, measured from when the contact last entered the flow. Cart abandonment repeats: the same shopper may abandon several carts in a week and each one is worth recovering, so **No re-entry** leaves money on the table. But someone who abandons three carts in an afternoon should not receive three overlapping recovery sequences. A 1-day cooldown keeps the flow earning without turning it into a nuisance. ## The proven 3-email sequence | Email | When | Angle | | ------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------- | | **1. Reminder** | 30–60 minutes after abandonment | "You left something behind" — show the cart, one button back to checkout. No discount yet. | | **2. Objection handling** | \~24 hours | Answer the hesitation: shipping policy, returns, reviews, guarantees. Still no discount for most stores. | | **3. Incentive** | 48–72 hours | A unique coupon with a real expiry — "10% off, valid 48 hours." The last word in the conversation. | Don't lead with the discount. A meaningful share of shoppers come back from email 1 at full price — leading with a coupon trains customers to abandon carts on purpose. ## Make the emails work harder * Use the **abandoned checkout merge tags** so every email shows the actual cart and a working recovery link — see [Personalization](/campaigns/personalization). * Keep the subject lines human: "Forgot something, Jane?" outperforms "COMPLETE YOUR PURCHASE NOW". * Send from a recognizable [sender address](/email-setup/sender-addresses) on your own domain. ## Why a cart might not be captured Recovery email requires an email address. Shoppers who bounce before the contact step of checkout can't be emailed — this is normal and affects every platform. The Shopify web pixel and signup forms increase the share of identified shoppers. Retainful never sends marketing to [suppressed contacts](/audience/suppressed-contacts), including recovery emails. Check **Integrations** — if your store shows disconnected, checkout events aren't arriving. Reconnect and test with a fresh checkout. ## Measure it Open the automation's [analytics](/automations/analytics) to see entries, email engagement, and — the number that matters — **recovered revenue**. A typical store recovers 5–15% of abandoned checkouts with a well-tuned flow. # Automation analytics Source: https://help.retainful.com/automations/analytics Measure each flow's entries, engagement, conversions, and revenue. Every automation reports its own performance. Open an automation and switch to its analytics view, or compare all automations side by side under **Automations → Analytics**. ## Flow-level metrics | Metric | What it tells you | | ---------------------------------- | ------------------------------------------------------------ | | **Entries** | How many contacts the trigger has pulled into the flow. | | **Completed** | How many reached the end (or a goal-based exit). | | **Emails sent / opened / clicked** | Engagement across all messages in the flow. | | **Conversions** | Contacts who completed the goal — typically a purchase. | | **Revenue** | Orders attributed to the flow, including coupon redemptions. | ## Per-step performance Each email step shows its own sends, opens, and clicks — so you can see exactly where a sequence loses people. The usual pattern: email 1 performs strongly and each follow-up tapers. What you're looking for is a *cliff*: * **Sharp drop in opens** at one email → its subject line needs work, or the preceding delay is too long. * **Good opens but no clicks** → the content or offer isn't landing. * **Most contacts stuck at a Binary split going one way** → your condition may not be checking what you think. ## Judging a flow honestly * Compare **revenue per entry**, not just totals — a flow with fewer entries can still be your most efficient. * Give changes a full cycle before judging — a 3-email, 4-day flow needs at least a couple of weeks of data. * Beware of celebrating opens. The goal is conversions; a flow with modest opens and strong coupon redemptions is winning. Change one thing at a time — a subject line, a delay, an offer — and note when you changed it. With multiple simultaneous edits you'll never know what worked. For how attribution windows and models are configured, see your **Settings → Attribution** preferences — the same rules apply to campaigns and automations. # Coupons Source: https://help.retainful.com/automations/coupons Generate unique, single-use discount codes in your store and deliver them inside your automations. The **Coupon** step creates a real discount code in your Shopify or WooCommerce store — unique to each contact, single-use, and with an expiry you control. Because the code is created in your store, it works at checkout like any other discount. ## Why unique codes beat shared codes * **No leaks** — a shared code like `SAVE10` ends up on coupon sites within hours. A unique code works once, for one person. * **Real urgency** — each code carries its own expiry date, so "expires in 48 hours" is actually true. * **Clean attribution** — when a unique code is redeemed, you know exactly which automation and email drove the sale. ## Configure the coupon step | Setting | Options | | ---------------------- | ------------------------------------------------------------------------------------ | | **Discount type** | Fixed amount, percentage, or free shipping. | | **Applies to** | The entire order, specific products, or specific collections. | | **Minimum purchase** | Optional minimum order value or minimum quantity. | | **Code prefix** | Codes are auto-generated; the prefix keeps them recognizable, e.g. `WELCOME-8FK2D1`. | | **Activation** | Active immediately, or starting at a specific date. | | **Expiration** | Never, after a number of days, on a specific date, or up to one year. | | **Combinations** | Whether the code can stack with product, order, or shipping discounts (Shopify). | | **Exclude sale items** | Skip already-discounted products (WooCommerce). | ## Show the code in your messages After a Coupon step, use the coupon [merge tags](/campaigns/personalization) in any later email or WhatsApp step: * **Coupon code** — the contact's unique code, often inside a styled coupon block. * **Coupon expiry date** — reinforces urgency. * **Checkout link with coupon applied** — in cart recovery flows, one click restores the cart *and* applies the discount. Place the Coupon step immediately **before** the email that reveals it — the code is generated when the contact reaches the step, so its expiry countdown starts at the right moment. ## Recommended recipes * **Welcome offer** — 10% off, expires in 7 days, minimum purchase to protect margins. * **Cart recovery closer** — hold the discount until the *last* email; many shoppers return without one. * **Next-order coupon** — after a first purchase, a code valid for 30 days drives the second order, which is the hardest one to win. * **Win-back** — a stronger offer (15–20%) for customers inactive 90+ days, with a short expiry. ## Tracking redemptions When an order uses a generated code, the purchase is attributed to the automation and shows up in its [revenue analytics](/automations/analytics). # Create an automation Source: https://help.retainful.com/automations/create-an-automation Build a workflow on the visual canvas: pick a trigger, add steps, test, and publish. This guide builds an automation from scratch. If a [template](/automations/overview#start-from-a-template) covers your use case, start there instead — you can customize everything after. ## 1. Create the workflow Go to **Automations** and click **Create automation**. Give it a name that describes its job — "Welcome series", "Post-purchase thank you". ## 2. Configure the trigger Every flow starts with one trigger event: Choose where the event comes from (your store, Retainful itself, or a connected integration) and which event starts the flow — for example **Checkout started**, **Order placed**, or **Subscribed to list**. The full catalog is in [Triggers](/automations/triggers). Narrow which events qualify — for example, only orders over \$50, or only signups to a specific list. Require the contact to match conditions — for example, only first-time customers, or only contacts in a [segment](/audience/segments). Decide whether a contact who finishes the flow can enter it again — never, always, or only after a cooldown (hours, days, or weeks). ## 3. Add steps Click the **+** button on any connection to insert a step. Build your sequence from [the step library](/automations/steps): send an email, wait a day, branch on a condition, generate a coupon, and so on. A simple welcome series looks like: 1. **Trigger:** Subscribed to list "Newsletter" 2. **Email:** "Welcome — here's what we're about" 3. **Delay:** 2 days 4. **Email:** "Our customers' favorites" (with a product block) 5. **Delay:** 3 days 6. **Binary:** Has the contact placed an order? * **No → Email:** "Here's 10% off your first order" (with a coupon) * **Yes →** exit ## 4. Write the emails Click any email step to set its subject, sender, and content. The full [email editor](/campaigns/email-editor) opens — same blocks, personalization, and templates as campaigns. Cart and coupon [merge tags](/campaigns/personalization) are available where the trigger provides them. ## 5. Test the flow Before publishing: * **Send test emails** from each email step. * Walk the canvas end to end: does every branch lead somewhere sensible? Are your delays realistic? * Trigger it for real if you can — for example, place a test checkout on your store for a cart recovery flow. ## 6. Publish Click **Publish**. From this moment, new trigger events enter the flow. Contacts already mid-flow continue even if you later pause the automation for new entries. Editing a published automation creates a new version — contacts who entered before your edit finish on the path they started, while new contacts get the updated flow. ## Monitor and iterate Give a new automation a week, then open its [analytics](/automations/analytics). The most common improvements: tightening the first delay, rewriting the first subject line, and adding a coupon to the final email of a recovery flow. # Automations overview Source: https://help.retainful.com/automations/overview Always-on workflows that respond to customer behavior — cart recovery, welcome series, win-backs, and more. An automation is a workflow that runs by itself. You set it up once; from then on, whenever a customer does something — abandons a cart, places a first order, joins a list — the automation responds with the right message at the right time. Campaigns are something you *send*; automations are something you *switch on*. ## How an automation works Every automation has three parts: 1. **A trigger** — the event that starts the flow for a contact, like "checkout started but not completed" or "subscribed to list". See [Triggers](/automations/triggers). 2. **Steps** — what happens next: send an email, wait two days, check a condition, generate a coupon. See [Steps](/automations/steps). 3. **Exit and re-entry rules** — when a contact should leave the flow early (for example, they completed their purchase) and whether they can enter it again later. You build all of this on a visual canvas — boxes connected by arrows, no code. ## Start from a template The fastest way to get value is the template gallery (**Automations → Templates**). Each template is a complete, proven flow with pre-written emails: | Template category | What it does | | ----------------------------- | ------------------------------------------------------------------ | | **Recover lost sales** | Abandoned cart recovery — the highest-ROI automation in ecommerce. | | **Nurture subscribers** | Welcome series for new signups, with an optional welcome discount. | | **Encourage repeat purchase** | Post-purchase thank-you and cross-sell flows, next-order coupons. | | **Win back customers** | Re-engage people who haven't bought in a while. | Templates list their **prerequisites** (like a connected store for cart recovery) and show whether each is fulfilled before you publish. Not sure which to run? The [Use cases](/automations/use-cases/overview) library walks through the most popular, highest-ROI automations — what each is for and the sequence that works. ## Draft, publish, pause * **Draft** — you're editing; nothing runs. * **Published (Active)** — live; new trigger events enter the flow. * **Paused** — no new contacts enter. ## Guides in this section Build a flow on the visual canvas, step by step. Every event that can start a flow, plus filters and re-entry rules. Emails, WhatsApp, coupons, delays, branches, webhooks, and list updates. A library of proven, high-ROI automations — cart recovery, welcome, win-back, and more. Generate unique, single-use discount codes inside your flows. Measure each flow's engagement and revenue. # Steps Source: https://help.retainful.com/automations/steps The building blocks of every flow: actions, timing, and logic. Steps are what an automation *does* after the trigger fires. Insert a step anywhere by clicking the **+** on a connection in the canvas. Steps fall into three groups. ## Actions ### Send Email Sends an email to the contact. Configure the subject, sender, and content with the full [email editor](/campaigns/email-editor) — including product blocks, coupons, and [personalization](/campaigns/personalization). ### Send WhatsApp Message Sends an approved [WhatsApp template message](/whatsapp/message-templates). Requires a connected WhatsApp Business account; only contacts with a phone number and WhatsApp opt-in receive it. ### Coupon Generates a **unique, single-use discount code** in your store for this contact, which you can show in any later email or WhatsApp message in the flow. Full options in [Coupons](/automations/coupons). ### List Update Adds the contact to a list or removes them from one. Useful for marking milestones ("completed welcome series") or chaining flows — joining a list can trigger another automation. ### Webhook Sends the contact and event data to any URL you choose — notify your own systems, a Slack channel, or a third-party tool when a contact reaches this point. Details for developers: [Webhooks](/developers/webhooks). ## Timing ### Delay Pauses the contact at this point before continuing. Three modes, which combine: * **Relative** — wait an amount of time: 30 minutes, 2 hours, 3 days. * **Specific time of day** — continue at, say, 9:00 AM in a timezone you pick. "Wait 1 day, then continue at 9 AM" sends the next email the following morning. * **Specific days** — only continue on selected weekdays, so messages never land on weekends. For cart recovery, the first delay should be short — 30–60 minutes. The shopper's intent fades fast. ## Logic ### Binary (if/else) Splits the flow into **Yes** and **No** paths based on a condition — contact properties, segment membership, or what's happened so far in the flow. Classic patterns: * **Purchased since the last email?** Yes → thank them / exit. No → send the discount. * **Opened the first email?** Yes → softer follow-up. No → new subject line entirely. * **VIP segment member?** Yes → bigger reward. No → standard offer. Each branch can contain any number of further steps, including more binary splits. ## Putting it together ```text Example: post-purchase flow theme={null} Trigger: Order placed (first order only) → Delay: 1 hour → Email: "Thanks for your order!" → Delay: 7 days, continue at 10 AM → Binary: placed a second order? Yes → List Update: add to "Repeat customers" → exit No → Coupon: 10% off, expires in 7 days → Email: "Here's 10% off your next order" ``` Every step reports its own performance — see [Automation analytics](/automations/analytics). # Triggers Source: https://help.retainful.com/automations/triggers Every event that can start an automation, plus filters, audience rules, and re-entry settings. A trigger is the event that pulls a contact into your automation. Each automation has exactly one trigger; the trigger's settings control who gets in and how often. ## Trigger events What's available depends on your connected integrations: ### Store events (Shopify / WooCommerce) | Event | Fires when | Typical use | | ------------------------------ | -------------------------- | ---------------------------------------------------------------- | | **Checkout started** | A shopper begins checkout. | [Abandoned cart recovery](/automations/abandoned-cart-recovery). | | **Order placed** | A new order is created. | Thank-you and cross-sell flows. | | **Order paid** | Payment is confirmed. | Receipts-adjacent follow-ups. | | **Order fulfilled** | The order ships. | Delivery follow-up, review requests. | | **Order cancelled / refunded** | An order is reversed. | Service recovery flows. | | **Product back in stock** | Inventory returns. | Back-in-stock alerts to waiting customers. | ### Retainful events | Event | Fires when | | --------------------------------- | -------------------------------------------------------------------------------------- | | **Subscribed to list** | A contact joins a specific list — the classic welcome-series trigger. | | **Subscribed to email marketing** | A contact opts in to marketing. | | **Email opened / clicked** | A contact engages with a campaign or flow email — great for interest-based follow-ups. | ### Custom events Any event your own systems send through the [Events API](/developers/api/events) can trigger an automation — a booking confirmed, a subscription renewed, a quiz completed. The event's data is available for filters and personalization. ## Trigger filters Filters narrow which events qualify, using the event's own data. Examples: * Order total **is greater than** 100 * Checkout currency **is** USD * List **is** "VIP waitlist" An event that doesn't pass the filters simply doesn't start the flow for that contact. ## Audience filters Where trigger filters look at the **event**, audience filters look at the **contact** — their properties, segment membership, or history. Example: only enter contacts whose order count is 1 (first-time buyers). ## Re-entry rules What happens when the same contact triggers the flow again? | Setting | Behavior | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **No re-entry** | One trip through the flow, ever. Right for welcome series. | | **Allow re-entry** | Enter again any time the trigger fires (after finishing the previous run). | | **Re-entry after a cooldown** | Enter again only after a set number of hours, days, or weeks. Right for [cart recovery](/automations/abandoned-cart-recovery#re-entry) (1 day) and for win-back flows you don't want to repeat monthly. | A contact is only ever in one run of a given automation at a time — triggering it again mid-flow doesn't create a second parallel run. ## Exit early Flows can drop a contact out before the end when the goal is reached — most importantly, a cart recovery flow should stop as soon as the shopper completes their purchase, so nobody gets a "you forgot something!" email about an order they already placed. Exiting on the goal is not automatic — it happens only if you configure an exit rule. For cart recovery, the rule to set is [Order paid **or** Order fulfilled since the start of the flow](/automations/abandoned-cart-recovery#exit-rule). # Back in stock Source: https://help.retainful.com/automations/use-cases/back-in-stock Tell waiting shoppers the moment a sold-out product returns — demand you've already captured, converted automatically. A sold-out product is demand you've already earned and can't yet fill. When shoppers ask to be notified, they're telling you they're ready to buy the instant it's available. A back-in-stock automation closes that loop on its own: the moment inventory returns, the people waiting hear about it first — often converting within minutes, before the item sells out again. ## When to use it * You carry popular products that sell out and get restocked. * You let shoppers request a "notify me when available" alert on out-of-stock items. * You want to recover sales that would otherwise quietly vanish when an item runs out. ## How it works On an out-of-stock product, a shopper submits their email through a "notify me" [signup form](/forms/overview). They're added to a [list](/audience/lists) tied to that product. When inventory returns, your store fires the **Product back in stock** event for that item. Everyone waiting gets an email right away: "It's back — and going fast." One click takes them straight to the product. A short follow-up to non-buyers reinforces scarcity — restocked favorites often sell out again quickly. ## Set it up 1. Add a "notify me when available" [signup form](/forms/create-a-form) to your out-of-stock product pages, feeding a waitlist [list](/audience/lists). 2. Create an automation with the **Product back in stock** trigger (see [Triggers](/automations/triggers)). 3. Filter to the right audience — the contacts who asked about that product. 4. Write the alert email with the product block and a direct shop link. 5. **Publish.** ## The proven sequence | Email | When | Angle | | ----------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------- | | **1. It's back** | Immediately on restock | "Good news — it's back in stock." Lead with the product image and one button to buy. Speed is everything. | | **2. Going fast** | \~24 hours, only if not purchased | "Still available — but not for long." Reinforce scarcity for the people who didn't act yet. | Send the first alert the *instant* stock returns, not on a schedule. Back-in-stock emails convert because they're timely — a restocked best-seller can sell out again in hours, and the waitlist is racing everyone else to it. ## Make it work harder * **Capture the waitlist well.** The flow is only as strong as the list feeding it — make the "notify me" form prominent on every out-of-stock page. * **Keep it product-specific.** Alert only the people who asked about *that* item. A blast to everyone reads as noise; a precise alert reads as a favor. * **Lean on scarcity, honestly.** "Limited quantities" works because it's true — restocks are often smaller than the original run. ## Measure it Open the automation's [analytics](/automations/analytics) to see how much of the waitlist converts and the **revenue recovered** from restocks. This is demand that would otherwise have disappeared the moment the item sold out — every order here is a save. # Automation use cases Source: https://help.retainful.com/automations/use-cases/overview A library of proven, high-ROI automations — what each one is for, when to use it, and the sequence that works. These are the automations worth setting up first. Each one targets a moment in the customer lifecycle where a timely email earns real revenue — and each has a [template](/automations/overview#start-from-a-template) so you can launch it in minutes and customize after. Start with the first two; they cover the highest-value moments for almost every store. ## The essentials Win back the \~70% of shoppers who start checkout and leave. The single highest-ROI automation in ecommerce. Greet new subscribers, introduce your brand, and turn the signup discount into a first order. Thank new customers, set expectations, and recommend the perfect next product. Nudge customers to reorder consumables right as they're about to run out. Re-engage customers who used to buy but have gone quiet — before you lose them for good. Tell waiting shoppers the moment a sold-out product returns, while intent is still high. ## How to choose | If your goal is… | Run this | Trigger | | --------------------------------- | ------------------------------------------------------------------ | ----------------------- | | Recover almost-sales | [Abandoned cart recovery](/automations/abandoned-cart-recovery) | Checkout started | | Convert new subscribers | [Welcome series](/automations/use-cases/welcome-series) | Subscribed to list | | Increase repeat purchases | [Post-purchase & cross-sell](/automations/use-cases/post-purchase) | Order placed | | Sell more consumables | [Replenishment reminder](/automations/use-cases/reorder-reminder) | Order placed | | Reactivate lapsed customers | [Win-back](/automations/use-cases/win-back) | Order placed + cooldown | | Recover demand for sold-out items | [Back in stock](/automations/use-cases/back-in-stock) | Product back in stock | You don't have to choose just one. These flows run side by side without colliding — a contact is only ever in one run of a given automation at a time, and cart recovery exits the moment someone buys. ## The shape they share Most of these follow the same proven skeleton, which the templates already encode: 1. **Trigger** — the moment that matters (see [Triggers](/automations/triggers)). 2. **A short first delay**, then the first email. 3. **One or two follow-ups**, spaced a day or more apart. 4. **A [Binary](/automations/steps#binary-ifelse) check** — has the goal happened (a purchase, a reorder)? If yes, exit; if no, continue. 5. **An optional [coupon](/automations/coupons)** in the final message to close the loop. Once a flow is live, give it a week and open its [analytics](/automations/analytics) to tune the first delay and subject line. # Post-purchase & cross-sell Source: https://help.retainful.com/automations/use-cases/post-purchase Turn a first order into a second one — thank customers, set expectations, and recommend the perfect next product. The moment after someone buys is the warmest you'll get. They've trusted you with their money and they're watching for confirmation — post-purchase emails earn some of the highest engagement of any flow. A good post-purchase series does three jobs: it reassures, it deepens the relationship, and it sets up the second order, which is the hardest and most valuable one to win. ## When to use it * You want new customers to feel taken care of after checkout. * You sell products with natural companions ("bought a camera → needs a case"). * You're trying to lift repeat-purchase rate and customer lifetime value. This flow is for relationship and cross-sell — not the order receipt itself. Transactional receipts are sent by your store. Use this automation to start the *next* conversation. ## How it works The **Order placed** trigger starts the flow. Add an audience filter for **first order only** to give new customers a distinct welcome-to-the-family experience. Shortly after the order, a genuine thank-you — what happens next, how to get help, and a reason to feel good about the choice. A few days later, recommend the products that pair with what they bought, with a product block and a gentle incentive. A [Binary](/automations/steps#binary-ifelse) step can check whether they've ordered again — repeat buyers get added to a "Repeat customers" [list](/audience/lists); the rest get a nudge. ## Set it up 1. Go to **Automations → Templates** and choose **Order Follow Up** (thank-you + cross-sell) or **Product Follow Up** (recommendations tied to what they bought). 2. Set the trigger to **Order placed**. Add an audience filter on order count if you want to target first-time buyers only. 3. Write the thank-you, then the cross-sell email with a product block. 4. Optionally add a [Coupon](/automations/coupons) step — a small next-order discount — before the cross-sell. 5. **Publish.** ## The proven sequence | Email | When | Angle | | --------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------- | | **1. Thank you** | \~1 hour after the order | "Thanks for your order!" Reassure, set delivery expectations, point to support. No selling yet. | | **2. Get the most from it** | \~3 days | Tips, how-to, or care advice for what they bought — useful first, commercial second. | | **3. You might also like** | \~7 days | Cross-sell complementary products with a product block, optionally a small next-order coupon. | Recommend *companions*, not replacements. Someone who just bought running shoes wants socks and insoles — not a different pair of shoes. Relevance is what makes a cross-sell feel helpful instead of pushy. ## Make it work harder * **Branch on behavior.** Use a [Binary](/automations/steps#binary-ifelse) step: already ordered again? Add them to **Repeat customers** and exit. Not yet? Send the cross-sell with an incentive. * **Time it to delivery.** A "how are you enjoying it?" email lands best *after* the product arrives — trigger on **Order fulfilled** instead of **Order placed** for review requests. * **Protect margins.** If you add a next-order coupon, keep it modest and set a minimum purchase in the [coupon](/automations/coupons) step. ## Measure it Open the automation's [analytics](/automations/analytics) and watch the **repeat-purchase revenue** it drives. The number that matters here isn't opens — it's how many first-time buyers come back for a second order. # Replenishment reminder Source: https://help.retainful.com/automations/use-cases/reorder-reminder Remind customers to restock consumables right as they're about to run out — predictable, recurring revenue on autopilot. If you sell anything people use up — coffee, supplements, skincare, pet food, refills — there's a predictable moment when each customer is about to run out. A replenishment reminder reaches them right then, with a one-click path back to the same product. It's effortless revenue: you're not convincing anyone, just showing up at the perfect time. ## When to use it * You sell consumables or products with a natural repurchase cycle. * You know roughly how long a typical order lasts (a 30-day supply, a month's worth of food). * You want to lift repeat-purchase rate without discounting every order. ## How it works The **Order placed** trigger starts the flow for the customer who just bought. A [Delay](/automations/steps#delay) holds them for the length of a typical cycle — say 25 days for a 30-day product, so the reminder arrives just *before* they run dry. "Running low? Reorder in one click." Link straight back to the product, pre-filled where possible. A [Binary](/automations/steps#binary-ifelse) step checks whether they've already reordered — if they have, they exit; if not, a follow-up (optionally with a small incentive) seals it. ## Set it up 1. Go to **Automations → Templates** and choose **Remind customers to reorder** (or build from the **Order placed** trigger). 2. Add a trigger filter for the relevant products, if only some of your catalog is consumable. 3. Set the [Delay](/automations/steps#delay) to fire shortly *before* the product typically runs out — a few days of lead time beats a few days late. 4. Optionally branch on "has reordered?" and add a [Coupon](/automations/coupons) for the holdouts. 5. **Publish.** ## The proven sequence | Email | When | Angle | | ---------------------- | --------------------------------------- | --------------------------------------------------------------------------------- | | **1. Time to restock** | Just before the supply runs out | "You're about to run low — reorder in one click." Make repurchasing frictionless. | | **2. Still need it?** | \~5–7 days later, only if not reordered | A reminder with a small "thanks for coming back" incentive to tip them over. | Get the timing from your own data, not a guess. If a product is a 30-day supply, send around day 25 — early enough that they never actually run out, late enough that it feels timely. ## Make it work harder * **Match the delay to the product.** A coffee subscription and a tube of moisturizer have very different cycles — run separate flows (or trigger filters) so each reminder lands at the right moment. * **Make reordering one click.** The less friction between "I should reorder" and "done," the more revenue this flow earns. * **Hold the discount.** Many customers reorder at full price simply because you reminded them — save any incentive for the second email and the people who didn't bite. ## Measure it Open the automation's [analytics](/automations/analytics) and track **reorder revenue** and the share of customers who repurchase. A well-timed replenishment flow is one of the most reliable revenue lines you can build. # Welcome series Source: https://help.retainful.com/automations/use-cases/welcome-series Turn a fresh subscriber into a first-time buyer — the automation with the highest open rates you'll ever send. When someone joins your list, they've just raised their hand. They're more interested in you right now than they may ever be again — welcome emails see open rates several times higher than regular campaigns. A welcome series capitalizes on that attention: it introduces your brand, delivers the discount you promised at signup, and gives a clear reason to place a first order. ## When to use it * You run a [signup form](/forms/overview) or popup that offers a welcome discount. * You want every new subscriber to get the same strong first impression, automatically. * You'd like to convert the signup incentive into an actual sale before it's forgotten. ## How it works A visitor joins a [list](/audience/lists) — usually through a [signup form](/forms/create-a-form) with a welcome offer, or at checkout. The **Subscribed to list** trigger pulls them in. Set re-entry to **No re-entry** so each person gets the series exactly once. A warm hello, your story and best-sellers, and a reminder of their discount with a real expiry — spaced over a few days. A [Binary](/automations/steps#binary-ifelse) step checks whether they've ordered. If they have, they exit early; if not, the final email makes the offer impossible to ignore. ## Set it up 1. Go to **Automations → Templates** and choose **Welcome Series** (or build from scratch with the **Subscribed to list** trigger). 2. Point the trigger at the list your [signup form](/forms/create-a-form) feeds. 3. If you offer a welcome discount, add a [Coupon](/automations/coupons) step before the email that reveals it — a unique, single-use code beats a shared `WELCOME10`. 4. Set re-entry to **No re-entry** and **Publish**. ## The proven 3-email sequence | Email | When | Angle | | ------------------------------ | ----------- | ----------------------------------------------------------------------------------------------------- | | **1. Welcome + your discount** | Immediately | "Welcome — here's your 10% off." Deliver the promised code right away while excitement is highest. | | **2. Who you are** | \~2 days | Tell your brand story and show your best-sellers with a product block. Build trust, not just urgency. | | **3. Last chance** | \~4–5 days | "Your welcome offer expires soon." Restate the code and its expiry, with one clear button to shop. | Deliver the discount in the **first** email, not the third. Subscribers signed up *for* the offer — making them wait is the fastest way to lose them. Save the "expiring" urgency for the final send. ## Make it work harder * **Connect the form to the flow.** When your [signup form](/forms/create-a-form) adds people to the same list this automation triggers on, the relationship starts the instant they subscribe. * **Use double opt-in where required.** If you've enabled [double opt-in](/audience/contacts#single-vs-double-opt-in), the series begins only after the contact confirms. * **Personalize.** Use first-name and coupon [merge tags](/campaigns/personalization) so the code and greeting are theirs alone. * **Send from your domain.** A recognizable [sender address](/email-setup/sender-addresses) lands the welcome where it belongs — the inbox. ## Measure it Open the automation's [analytics](/automations/analytics) to watch open and click rates (welcome emails should be your strongest) and the **revenue** from first orders. If email 1 converts well but the series doesn't, shorten the gaps; if few redeem the code, strengthen the offer or its urgency. # Win-back Source: https://help.retainful.com/automations/use-cases/win-back Re-engage customers who used to buy but have gone quiet — winning one back costs far less than finding someone new. Every store has them: customers who bought once or twice, then drifted away. They already know and trust you, which makes them far cheaper to reactivate than a stranger is to acquire. A win-back flow reaches lapsed customers at the point where they're slipping from "quiet" to "gone," reminds them what they're missing, and gives a reason to come back now. ## When to use it * You have customers who haven't purchased in a while (often 60, 90, or 120+ days, depending on your buying cycle). * You'd rather reactivate a known customer than pay to acquire a new one. * You want to keep your list healthy by re-engaging — or cleanly sunsetting — dormant contacts. ## How it works There are two solid ways to build this; pick the one that fits how you think about "lapsed." Trigger on **Order placed**, then a long [Delay](/automations/steps#delay) — say 90 days. A [Binary](/automations/steps#binary-ifelse) step then asks "have they ordered again?" If yes, they exit; if no, the win-back emails go out. This needs only the store events every plan has. Build a [segment](/audience/segments) of lapsed customers (e.g. "last order more than 90 days ago"). Trigger the flow when contacts enter that segment, and use **re-entry after a cooldown** so the same person isn't re-engaged too often. ## Set it up 1. Decide what "lapsed" means for your store — base it on your typical time between orders, not a round number. 2. Create the automation: either **Order placed → long delay → Binary "ordered since?"**, or trigger on entry to a lapsed-customer [segment](/audience/segments). 3. Write the sequence below. Lead with connection, not a discount. 4. Add a [Coupon](/automations/coupons) step before the final email — a stronger, short-expiry offer for the people who didn't respond to the softer touch. 5. Set re-entry to **after a cooldown** (e.g. 90 days) so you don't pester the same people, and **Publish**. ## The proven 3-email sequence | Email | When | Angle | | ------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **1. We miss you** | At the lapse point | Warm and human — "It's been a while." Remind them what they loved, show new arrivals or best-sellers. No discount yet. | | **2. Here's something for you** | \~4 days | A real incentive: a stronger coupon (15–20%) with a short expiry. This is the offer that does the heavy lifting. | | **3. Last call** | \~7 days | "Your offer expires tonight." Final reminder, clear deadline. Decide here whether non-openers should be [suppressed](/audience/suppressed-contacts) to protect deliverability. | Win-back is the one flow where leading with a generous discount is justified — these customers have already stopped buying, so a softer touch alone often isn't enough. Make the offer count, but keep the expiry short. ## Make it work harder * **Make it a true offer.** Win-back discounts can be more generous than your welcome offer — you're competing against "they've forgotten us," not against full price. * **Keep your list clean.** Contacts who ignore the whole series are telling you something. Moving long-term non-responders to [suppressed](/audience/suppressed-contacts) protects your sender reputation and your deliverability for everyone else. * **Personalize the reminder.** Referencing what they bought before ("your favorite is back in stock") beats a generic "we miss you." ## Measure it Open the automation's [analytics](/automations/analytics) and watch **reactivated revenue** — orders from customers who'd gone quiet. Even a modest reactivation rate is high-margin revenue you'd otherwise have lost entirely. # Campaign analytics Source: https://help.retainful.com/campaigns/analytics Read your campaign results: delivery, opens, clicks, bounces, and attributed revenue. Open any campaign to see how it performed. Here's what each number means and what to do about it. ## The metrics | Metric | What it means | Healthy range | | ---------------- | --------------------------------------------------------------------- | ----------------------------- | | **Delivered** | Emails accepted by the recipient's mail server. | 98%+ | | **Opened** | Recipients who opened the email. | 20–40% | | **Clicked** | Recipients who clicked a link. | 2–5% | | **Bounced** | Emails rejected — invalid address (hard) or temporary failure (soft). | Under 2% | | **Unsubscribed** | Recipients who opted out from this email. | Under 0.5% | | **Revenue** | Sales attributed to this campaign. | The number that matters most. | Apple Mail and some other clients pre-load emails, which registers an open even if the person never read it. Treat opens as a trend indicator and judge success by **clicks and revenue**. ## How revenue attribution works When a contact receives your campaign and then places an order within the attribution window, the order's value is credited to the campaign. You can choose the attribution model (for example, last-touch) and window under **Settings → Attribution**. ## The recipients view Beyond aggregate numbers, the **Recipients** tab lists every contact with their individual delivery status — delivered, opened, clicked, bounced. From here you can: * **Filter** by engagement (for example, everyone who didn't open). * **Move recipients to a list** in bulk — build a "clicked spring sale" list for follow-up. * **Create a new campaign from these recipients** — the fastest way to run a resend-to-non-openers play. ## Reading the results Usually a subject line or deliverability issue. Test a different subject on non-openers. If opens are low across all campaigns, check your [domain verification](/email-setup/sending-domains) and list health. The subject made a promise the content didn't deliver, or the call to action is buried. Try one clear button above the fold. Your list has stale or invalid addresses — common after importing an old list. Bounced addresses are [suppressed automatically](/audience/suppressed-contacts); consider a re-permission pass on old contacts. Audience mismatch — the wrong segment got the message, or you're emailing too often. Tighten your targeting with [segments](/audience/segments). For performance across **all** campaigns and automations in one view, see the **Analytics** page in the sidebar. # Create a campaign Source: https://help.retainful.com/campaigns/create-a-campaign Step-by-step: design, target, test, and send your first email campaign. This walkthrough covers the full journey from a blank campaign to a scheduled send. Before your first campaign, make sure you have a verified [sender address](/email-setup/sender-addresses) and ideally a verified [sending domain](/email-setup/sending-domains). ## 1. Pick a template Go to **Campaigns → Create campaign**. The template gallery opens — browse, sort, or search, then pick a starting point. Every part of a template can be changed, so choose for layout rather than color. You can also start from one of your own saved [templates](/campaigns/email-editor#save-and-reuse-templates). ## 2. Design your email The editor opens with your chosen template. Depending on the template type you'll be in the **drag-and-drop editor**, the **rich text editor**, or the **HTML editor** — see [Email editor](/campaigns/email-editor) for what each can do. The essentials for every campaign: * **Subject line** — the single biggest factor in whether the email gets opened. Keep it under \~50 characters and specific. * **Preview text** — the gray snippet shown next to the subject in the inbox. Don't waste it on "View in browser". * **From name and address** — pick a verified sender your customers will recognize. * **Unsubscribe link** — included automatically in the footer; required by law and by mailbox providers. ## 3. Choose your audience Select who receives the campaign: one or more [lists](/audience/lists), a [segment](/audience/segments), or your whole subscribed audience. Retainful automatically excludes [unsubscribed and suppressed contacts](/audience/suppressed-contacts), so you can't accidentally email someone who opted out. ## 4. Configure tracking Under campaign settings you can add **UTM parameters** to every link, so the campaign's traffic and sales show up clearly in Google Analytics and your store's reports. ## 5. Test before you send Click **Send test email** and send it to yourself (and a teammate). Check: * Subject and preview text look right in the inbox list. * Images load and buttons work. * Personalization renders correctly — including for contacts with no first name (set a [fallback](/campaigns/personalization)). * It reads well on a phone. ## 6. Review and send The **Review** step shows everything in one place — audience size, sender, subject, and a final preview. Click **Send** and the campaign starts going out in batches. Status changes to **Sending**, then **Sent**. Pick a date and time. Scheduled campaigns can be edited or cancelled any time before they start sending. Mid-morning in your customers' timezone is a reliable default. After sending, watch the first hour in [campaign analytics](/campaigns/analytics) — if something's wrong (broken link, wrong audience), pause the campaign and fix it before the rest goes out. ## Re-target from a previous campaign From any sent campaign's recipient list you can create a follow-up campaign — for example, target everyone who **didn't open** with a different subject line a few days later, or move everyone who clicked into a list for a special offer. # Email editor Source: https://help.retainful.com/campaigns/email-editor Design emails with drag-and-drop blocks, rich text, or raw HTML — plus product blocks, coupons, and reusable templates. Retainful gives you three ways to build an email. All three are available for campaigns, automation emails, and saved templates. ## Choose your editor | Editor | Best for | | ----------------- | ------------------------------------------------------------------------------- | | **Drag-and-drop** | Most people, most of the time. Visual building with blocks — no code. | | **Rich text** | Simple, personal-feeling emails that look like they were written in a mail app. | | **HTML** | Developers and designers who want pixel-level control with their own code. | ## The drag-and-drop editor Drag blocks from the sidebar onto the canvas, then click any block to edit its content and style. ### Available blocks * **Text** — headings and paragraphs with full formatting. * **Image** — upload images or pick from your media library. * **Button** — your call to action; link it to any URL. * **Divider** — visual separation between sections. * **Coupon** — displays a discount code, including unique codes generated by [automations](/automations/coupons). * **Product** — pulls real products from your connected store, with image, name, and price. Choose products manually or use a dynamic feed (best sellers, newest, recently viewed). * **Custom HTML** — embed your own HTML inside an otherwise visual email. ### Working faster * **Auto-save** — the editor saves as you work; if your browser crashes, you can restore your progress. * **Universal blocks** — save a block (like your header or footer) once and reuse it across emails. * **Personalization** — insert merge tags from the toolbar; see [Personalization](/campaigns/personalization). * **Preview** — check the desktop and mobile rendering before sending, and use **web view** to see the email as a standalone page. ## The rich text editor A focused writing surface with text formatting, links, images, and merge tags. Emails built here tend to feel more personal — great for founder notes and plain-text style announcements, which often get *better* engagement than heavily designed emails. ## The HTML editor Paste or write complete HTML email markup. You're responsible for email-client compatibility (tables, inline styles — the usual email quirks). Merge tags work in HTML too. Email clients are far stricter than web browsers. If you hand-code, test in multiple clients — what works in Chrome may break in Outlook. ## Save and reuse templates Any design can be saved as a template under **Templates**: * Build a branded base template once — logo, colors, footer — and start every campaign from it. * Templates are shared across campaigns and automations, so your branding stays consistent everywhere. * Duplicate a template to iterate on a new version without touching the original. # Campaigns overview Source: https://help.retainful.com/campaigns/overview One-time email sends: newsletters, promotions, announcements, and product launches. A campaign is a one-time email you send to a chosen audience — a newsletter, a sale announcement, a product launch. (For always-on emails that respond to customer behavior, see [Automations](/automations/overview).) ## Campaign lifecycle Every campaign moves through a simple lifecycle: 1. **Draft** — you're still working on it. Nothing sends. 2. **Scheduled** — queued for a future date and time. 3. **Sending** — going out to your audience in batches. 4. **Sent** — done; analytics keep accumulating as people open and click. You can **pause** a campaign mid-send and **resume** it later — useful if you spot a typo after hitting send. ## What you'll find on the Campaigns page * A list of all your campaigns with status, audience size, and headline stats. * **Create campaign** to start a new one from the template gallery. * Click any sent campaign to open its [analytics](/campaigns/analytics) — opens, clicks, revenue, and the full recipient list. ## Guides in this section The full walkthrough — from template to send. Design emails with drag-and-drop blocks, rich text, or raw HTML. Use merge tags to address every reader by name — and more. Understand opens, clicks, bounces, and attributed revenue. ## A few principles that pay off * **Send to segments, not your whole list.** Relevance drives opens; blanket sends drive unsubscribes. See [Segments](/audience/segments). * **Always send a test email first.** Check it on your phone — most of your customers will read it there. * **One clear call to action.** Emails with a single, obvious button outperform ones with five competing links. * **Verify your domain before your first big send.** See [Sending domains](/email-setup/sending-domains). # Personalization Source: https://help.retainful.com/campaigns/personalization Use merge tags to personalize every email with contact details, cart contents, and coupon codes. Personalization replaces placeholders in your email with each recipient's own information at send time. "Hi Jane" instead of "Hi there" — and much more. ## Insert a merge tag In any editor, open the **Personalization** menu in the toolbar and pick a field. A tag is inserted where your cursor is, and each recipient sees their own value. To write tags by hand, or to look up the exact syntax, fallbacks, and where each tag works, see the [Shortcodes reference](/email-setup/shortcodes). ## What you can personalize with | Category | Examples | | ------------------ | ----------------------------------------------------------------------------------------------------------------- | | **Contact fields** | First name, last name, email, city, country. | | **Custom fields** | Anything you've stored — loyalty tier, size preference. See [Custom fields](/audience/custom-fields). | | **Store & cart** | Shop URL, abandoned checkout link — used heavily in [cart recovery emails](/automations/abandoned-cart-recovery). | | **Coupons** | The unique discount code generated for this recipient, and its expiry date. See [Coupons](/automations/coupons). | ## Always set fallbacks Not every contact has every field — plenty of subscribers never gave you their name. A fallback value fills the gap: * With fallback "there": **"Hi Jane"** / **"Hi there"** * Without fallback: **"Hi Jane"** / **"Hi "** (awkward) Set a fallback whenever you insert a tag that might be empty. ## Cart recovery tags In abandoned cart automations, two tags do the heavy lifting: * **Abandoned checkout URL** — a one-click link that restores the shopper's cart exactly as they left it. * **Abandoned checkout URL with coupon** — the same link with a discount automatically applied at checkout, removing the last bit of friction. ## Test your personalization Send yourself a test email and check how tags render. Then imagine the email for a contact with missing data — if you're unsure, look at a sparse contact in your [audience](/audience/contacts) and picture their version of the email. Personalization goes beyond names. Segment-specific content — sending different campaigns to [different segments](/audience/segments) — is often more powerful than any merge tag. # Lists API Source: https://help.retainful.com/developers/api/contact-groups Manage list membership programmatically. Lists (contact groups) organize your audience — see the [merchant-side guide](/audience/lists) for the concepts. The API lets your systems manage membership: add a customer to "Active subscribers" when they start a plan, remove them when they cancel. All requests require [authentication](/developers/authentication). ## List your lists `GET /api/v1/contact-groups` Returns the lists and segments in your organization, with their IDs. Use this to find the list ID you need for the add-contact call. | Parameter | Default | Notes | | --------- | ------- | --------------------------------------------------- | | `page` | `1` | Page number. | | `limit` | `10` | Items per page. Values above 100 are capped at 100. | | `type` | `ALL` | `LIST`, `SEGMENT`, or `ALL`. | ```bash theme={null} curl "https://api.retainful.net/api/v1/contact-groups?type=LIST" \ -H "X-API-Key: $RETAINFUL_API_KEY" ``` `GET /api/v1/contact-groups/{id}` returns a single list. ## Add a contact to a list `POST /api/v1/contact-groups/{listId}/contacts` Creates or updates the contact **and** adds them to the list, in one call. Returns `200 OK`. The body is a **flat** contact object — the same field set as the [Contacts API](/developers/api/contacts). Do not wrap it in a `contact` object and do not send an array; both are rejected with `400`. The contact's email address. Required on this endpoint, even though the Contacts API accepts phone instead. Max 100 characters. Max 100 characters. `SUBSCRIBED`, `NON_SUBSCRIBED`, or `UNSUBSCRIBED`. See the warning below — always set this explicitly. 7–20 characters. Where the contact came from. Defaults to `api`. Every other [contact field](/developers/api/contacts#request) is accepted here too. ```bash Example request theme={null} curl -X POST "https://api.retainful.net/api/v1/contact-groups/019e92d4-5856-7c11-9feb-a3200364d61e/contacts" \ -H "Content-Type: application/json" \ -H "X-API-Key: $RETAINFUL_API_KEY" \ -d '{ "email": "jane@example.com", "first_name": "Jane", "last_name": "Smith", "email_opt_in": "SUBSCRIBED", "source": "web-store" }' ``` ### Response ```json 200 OK theme={null} { "message": "Successfully created contact and added to the contact group", "contact": { "email": "jane@example.com", "contactUUID": "01K8J5M02QYPEJE5YN3V944ZY9", "action": "created" } } ``` `action` is `created` or `updated`, telling you whether the contact already existed. Note this endpoint returns a plain object — unlike the Contacts API, which returns a JSON:API envelope. A `404` means the list ID doesn't exist. The contact may still have been created; it just isn't in that list. **Always send `email_opt_in` explicitly on this endpoint.** If you omit it, the result depends on whether the contact already existed: a brand-new contact is created as `NON_SUBSCRIBED`, but an existing contact is set to `SUBSCRIBED`. The same request body produces different consent outcomes depending on state you can't see. ## List membership as a trigger Joining a list can [trigger an automation](/automations/triggers) — which makes this API a simple, robust way to start flows from your backend: 1. Create a list like "Trial started" in the dashboard. 2. Build a welcome/onboarding automation triggered by **Subscribed to list → Trial started**. 3. From your backend, add users to the list when their trial begins. The trigger fires once per contact per list. Adding someone who's already a member won't fire it again. For richer use cases — where the triggering moment carries data you want in filters and emails — prefer [custom events](/developers/api/events). Lists-as-triggers shine when the only fact that matters is membership itself. ## Membership vs. consent Adding someone to a list does **not** make them marketable — their [subscription status](/audience/overview#subscription-status) still governs whether marketing email reaches them. Manage consent through the `email_opt_in` field. ## Contacts not showing up? Contacts are written synchronously — if you got a success response with a `contactUUID`, the contact exists. If you can't see it: 1. **Check `action` in the response.** A response without a `contact` object means you're not hitting this endpoint — verify the URL. 2. **Verify the list ID.** A wrong ID returns `404`; look it up with `GET /api/v1/contact-groups`. 3. **Match the API key to the organization** you're viewing in the dashboard. A key belongs to exactly one organization. 4. **Check `email_opt_in`.** Contacts created as `NON_SUBSCRIBED` are hidden by the default subscription filters on the Members tab. 5. **Clear filters on the Members view**, and hard-refresh — the dashboard caches client-side. # Contacts API Source: https://help.retainful.com/developers/api/contacts Create, fetch, and list contact profiles. Contacts (also called profiles) are the people in your audience. The Contacts API lets you create them, update them, and read them back. All requests require [authentication](/developers/authentication). ## Create or update a contact `POST /api/v1/customer/create` Upserts a contact and returns `201 Created`. If a contact with the same email or phone already exists, it's updated; otherwise it's created. This endpoint does **not** add anyone to a list — for that, see the [Lists API](/developers/api/contact-groups). ### Request You must send **`email` or `phone`** — at least one. Everything else is optional. The contact's email address. Required unless you send `phone`. Phone number, 7–20 characters. Required unless you send `email`. Needed for WhatsApp messaging. Your own identifier for this person (your database ID), for reconciling across systems. Max 100 characters. Max 100 characters. Email marketing consent: `SUBSCRIBED`, `NON_SUBSCRIBED`, or `UNSUBSCRIBED`. Defaults to `NON_SUBSCRIBED` when omitted. Only send `SUBSCRIBED` when the person actually opted in. SMS/WhatsApp consent, same values. Defaults to `NON_SUBSCRIBED`. Whether the contact is [suppressed](/audience/suppressed-contacts). Defaults to `false`. Whether to require email confirmation before the contact counts as subscribed. Defaults to `false`. Where this contact came from, e.g. `booking-platform`. Defaults to `api`. Date of birth as an ISO 8601 date, e.g. `1990-01-15`. `MALE`, `FEMALE`, or `OTHER`. Case-sensitive. Max 100 characters. Normalized to a country code on write. Max 100 characters. Max 100 characters. Max 255 characters. Max 255 characters. 3–20 characters. Key–value pairs stored as [custom fields](/audience/custom-fields) — usable in segments and personalization. Maximum 30 keys per request, string values. Omitting `email_opt_in` creates the contact as `NON_SUBSCRIBED`, and they will receive no marketing email. Set it explicitly whenever you have consent — this is the single most common reason API-created contacts appear to be ignored by campaigns. Unrecognized fields are rejected with `400`. Send `first_name`, not `firstName`. ```bash Example request theme={null} curl -X POST "https://api.retainful.net/api/v1/customer/create" \ -H "Content-Type: application/json" \ -H "X-API-Key: $RETAINFUL_API_KEY" \ -d '{ "email": "jane@example.com", "first_name": "Jane", "last_name": "Smith", "phone": "+14155550123", "external_id": "cust_8841", "email_opt_in": "SUBSCRIBED", "source": "api", "custom_properties": { "loyalty_tier": "gold", "signup_channel": "mobile-app" } }' ``` ### Response ```json 201 Created theme={null} { "data": { "type": "profile", "id": "01K8J5M02QYPEJE5YN3V944ZY9", "attributes": { "email": "jane@example.com", "phone": "+14155550123", "first_name": "Jane", "email_opt_in": "SUBSCRIBED", "source": "api", "created": "2026-06-11T06:27:50.000Z", "updated": "2026-06-11T06:27:50.000Z" }, "relationships": { "lists": { "links": { "related": ".../profiles/01K8J5M02QYPEJE5YN3V944ZY9/lists" } } } }, "links": { "self": ".../api/v1/customer/01K8J5M02QYPEJE5YN3V944ZY9" } } ``` Keep the `id` — it's the stable identifier for fetching this contact later, and for sending events with `contactUUID`. ### Custom properties * Maximum **30 keys per request**; more returns `400`. * Values are stored as strings. Numbers and booleans are converted; **nested objects are not** — they store as unusable text, so flatten them before sending. * Empty and whitespace-only values are dropped rather than stored. ## Get a contact `GET /api/v1/customer/{id}` ```bash theme={null} curl "https://api.retainful.net/api/v1/customer/01K8J5M02QYPEJE5YN3V944ZY9" \ -H "X-API-Key: $RETAINFUL_API_KEY" ``` Returns the same profile envelope as above, or `404` if no contact has that ID. ## List contacts `GET /api/v1/customer` Returns contacts in pages. | Parameter | Default | Notes | | --------- | ------- | ------------------------------------------------------ | | `page` | `1` | Page number. | | `limit` | `10` | Contacts per page. Values above 100 are capped at 100. | ```bash theme={null} curl "https://api.retainful.net/api/v1/customer?page=1&limit=50" \ -H "X-API-Key: $RETAINFUL_API_KEY" ``` For a one-time bulk load of an existing audience, the dashboard's [CSV import](/audience/import-contacts) is faster and friendlier to [rate limits](/developers/rate-limits) than looping over this API. # Events API Source: https://help.retainful.com/developers/api/events Track custom events that build timelines, power segments, and trigger automations. Events are timestamped records of things contacts do — in your store, your app, or anywhere else. Each event lands on the contact's [timeline](/audience/contacts), becomes available to [segments](/audience/segments), and can [trigger automations](/automations/triggers#custom-events). All requests require [authentication](/developers/authentication). ## Track an event `POST /api/v1/events` Records an event and attaches it to a contact, creating that contact if they don't exist yet. Returns `201 Created`. ### Request Human-readable event name, e.g. `Add to Cart`. Any non-empty string is accepted — see [How event names become types](#how-event-names-become-types). Idempotency key for this occurrence — a cart ID, checkout ID, or order ID. See [Idempotency](#idempotency); getting this wrong is the most common integration bug. Who the event belongs to. Must contain either `contactUUID`, or at least one of `email` / `phone`. Flat key-value object describing the event. Must not be empty, and must not contain nested objects. Optional human-readable description, used when registering the event schema. #### The `contact` object | Field | Notes | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `contactUUID` | Looks up an existing contact directly. If no contact has this ID, the request fails with `400 CONTACT_NOT_FOUND` — it does **not** create one. | | `email` | Used to match or create the contact. | | `phone` | Used to match or create the contact. | | `external_id` | Your own identifier for this person. | | `first_name`, `last_name` | Stored on the contact. | | `email_opt_in`, `phone_opt_in` | `SUBSCRIBED`, `NON_SUBSCRIBED`, or `UNSUBSCRIBED`. | | `is_suppressed`, `double_opt_in` | Booleans. | | `custom_properties` | Key-value pairs stored as [custom fields](/audience/custom-fields). | Field names are snake\_case. Unrecognized fields are rejected with `400`, so `emailAddress` or `firstName` will fail — use `email` and `first_name`. When this endpoint creates a new contact, `email_opt_in` defaults to `NON_SUBSCRIBED`. A contact created this way will not receive marketing email until they opt in. Send `email_opt_in` explicitly if you have consent. ```bash Example request theme={null} curl -X POST "https://api.retainful.net/api/v1/events" \ -H "Content-Type: application/json" \ -H "X-API-Key: $RETAINFUL_API_KEY" \ -d '{ "eventName": "Add to Cart", "uniqueIdentifier": "cart_12345", "contact": { "email": "jane@example.com", "first_name": "Jane" }, "eventData": { "product_id": "SKU-100", "product_name": "Blue T-Shirt", "quantity": "2", "price": "19.99", "currency": "USD" } }' ``` ### Response ```json 201 Created theme={null} { "data": { "type": "event", "id": "01K8J5M02QYPEJE5YN3V944ZY9", "attributes": { "event_type": "REST-API.ADD_TO_CART", "occurred_at": "2026-06-11T12:00:00.000Z", "profile_id": "01K8J5M02QYPEJE5YN3V944ZY9" } } } ``` The event row is written immediately. Schema registration and automation delivery happen asynchronously a moment later. ## Idempotency `uniqueIdentifier` is what stops a retried HTTP call from emailing your customer twice. Uniqueness is scoped to the combination of **your organization, the event type, and the identifier**. | You send | What happens | | ------------------------------------------------------------ | ------------------------------------------------------------------------- | | A new `uniqueIdentifier` | The event is stored and any matching automation triggers. | | The **same**`uniqueIdentifier` again, same `eventName` | The stored event's data is updated. Automations do **not** trigger again. | | The same `uniqueIdentifier` under a **different**`eventName` | Treated as a distinct event. Automations **do** trigger. | So: use a stable ID per real-world occurrence (`order_55501`), and a **new** ID when you want the flow to fire again. Two things still happen on a resubmit: the contact is upserted (so profile fields update), and the event schema is re-registered. Only the automation trigger is suppressed. ## How event names become types You send a human-readable `eventName`. Retainful normalizes it and prefixes it with your integration's key to produce the internal event type you'll see in the dashboard: | `eventName` you send | Internal event type | | -------------------- | ----------------------------- | | `Add to Cart` | `REST-API.ADD_TO_CART` | | `Checkout Started` | `REST-API.CHECKOUT_STARTED` | | `Purchase Completed` | `REST-API.PURCHASE_COMPLETED` | | `Placed Order` | `REST-API.PLACED_ORDER` | Normalization uppercases the name and replaces every run of non-alphanumeric characters with a single underscore. The `REST-API.` prefix comes from the integration the API key belongs to. **There is no fixed list of event names.** Any non-empty string works — `Quiz Completed` and `Subscription Renewed` are as valid as the commerce names above. Because names are normalized, `Add to Cart`, `add-to-cart`, and `ADD TO CART` all collapse to the same event type and share the same idempotency namespace. Pick one spelling and keep it stable — automations and segments are bound to the normalized type, so renaming an event orphans them. Keep event names under about 50 characters. The internal type is stored in a 64-character column and includes the integration prefix; longer names fail on write. ## eventData rules * **Must not be empty.** * **No nested objects.** A nested object is rejected with `400` and a message naming the offending field. Flatten it: `shipping_city` rather than `shipping: { city }`. * **Values may be strings, numbers, or booleans.** Types are inferred and preserved for use in automation filters, so send `"amount": 19.99` if you want to compare it numerically. * Useful keys: `product_id`, `product_name`, `quantity`, `price`, `currency`, `order_id`, `checkout_id`, `total`, `amount`. * Every key becomes available in automation trigger filters and email personalization. ## Register an event schema `POST /api/v1/events/register` Registers an event type and its shape **without** attaching it to a contact. Use it so the event's fields appear in the automation builder's filter and personalization pickers before any real event has fired — letting marketing build the flow while engineering ships the integration. The event name to register. An example payload. Retainful infers the field names and types from it. Optional description of the event. ```bash theme={null} curl -X POST "https://api.retainful.net/api/v1/events/register" \ -H "Content-Type: application/json" \ -H "X-API-Key: $RETAINFUL_API_KEY" \ -d '{ "eventName": "Appointment Booked", "description": "Fired when a customer books a consultation", "eventData": { "service": "Consultation", "staff": "Dr. Lee", "date": "2026-07-01", "value": "120.00" } }' ``` This endpoint registers a schema only. It does **not** store an event, does not appear on any contact's timeline, and does not trigger automations. It accepts neither `contact` nor `uniqueIdentifier` — sending either returns `400`. Use `POST /api/v1/events` for everything tied to a real shopper. ## Designing good events * **Name by fact, not intent**: `Subscription Renewed` (what happened), not `Send Renewal Email` (what you want done). Marketing decides the response in the automation builder. * **Keep names stable.** Renaming orphans the automations and segments built on the old type. * **Include the fields you'll filter on.** If a flow should only fire for high-value renewals, send `amount`. * **Send one event per real occurrence**, with a `uniqueIdentifier` that matches that occurrence's own ID. ## Event-triggered automations end to end 1. `POST /api/v1/events/register` with your event shape. 2. In the dashboard, create an [automation](/automations/create-an-automation) triggered by your event, with filters like `plan is annual`. 3. `POST /api/v1/events` from production whenever it happens, with a fresh `uniqueIdentifier` each time. 4. Watch contacts flow through in the automation's [analytics](/automations/analytics). ## Errors | Code | Meaning | | ----------------------------- | ------------------------------------------------------ | | `VALIDATION_ERROR` | A field is missing, the wrong type, or not recognized. | | `EVENT_NAME_REQUIRED` | `eventName` was empty. | | `CONTACT_REQUIRED` | No `contact` object was sent. | | `CONTACT_IDENTIFIER_REQUIRED` | `contact` had no `contactUUID`, `email`, or `phone`. | | `CONTACT_NOT_FOUND` | The `contactUUID` you sent doesn't match a contact. | See [Errors](/developers/errors) for the response shape. # Authentication Source: https://help.retainful.com/developers/authentication Create an API key and authenticate your requests. Every API request is authenticated with an **API key** that belongs to one organization. The key identifies which store's data you're working with — there's no separate org parameter to pass. ## Create an API key In the Retainful dashboard, go to **Integrations** and open (or create) the integration your system represents — for a bespoke backend, create a custom app. Click **Create API key**. Copy it immediately and store it in your secrets manager — treat it like a password. ## Use the key Send it on every request in the `X-API-Key` header: ```bash theme={null} curl "https://api.retainful.net/api/v1/customer" \ -H "X-API-Key: $RETAINFUL_API_KEY" ``` The header `Retainful-api-key` is accepted as an alias. Requests without a valid key receive `401 Unauthorized`. ## Key hygiene * **Server-side only.** Never ship an API key in browser JavaScript or a mobile app — anyone can read it there. Calls from your storefront should go through your own backend. * **One key per system.** Give your CRM sync and your booking platform separate keys, so you can rotate or revoke one without breaking the other. * **Rotate on departure.** If someone with access to the key leaves, revoke it from **Integrations → your app → API keys** and issue a new one. * **Environment variables**, not source code. Keys in git history live forever. ## Revoking Revoke any key from the same place you created it. Revocation is immediate — in-flight systems using the key start receiving `401`s on their next request. # Custom integrations Source: https://help.retainful.com/developers/custom-integrations Connect your own platform or internal tools to Retainful as a first-class integration. A custom integration packages your connection to Retainful — its API keys, its event types, its configuration — as a named app inside the dashboard. It's the right structure when you're connecting a real system (a booking platform, a subscription service, an internal CRM) rather than making ad-hoc API calls. ## Why bother with the wrapper? * **Scoped credentials** — each integration has its own API keys, independently rotatable and revocable. * **Named events** — events you [register](/developers/api/events#register-an-event-schema) under the integration show up in the automation builder grouped under your integration's name, with their fields typed and pickable. * **Status & health** — the dashboard shows the integration's connection state where merchants expect it. ## Create one Go to **Integrations → Create app**. Name it after the system it represents — "Bookings", "Subscription billing". Create an API key for it — see [Authentication](/developers/authentication). Call [`POST /events/register`](/developers/api/events#register-an-event-schema) for each event type your system emits, with a representative payload. From your system, [create contacts](/developers/api/contacts) and [send events](/developers/api/events) as things happen. ## Integration patterns Send `trial_started`, `subscription_activated`, `subscription_renewed`, `subscription_cancelled`. Build onboarding flows on trial start, renewal thank-yous, and cancellation win-backs — each with the plan and value data in filters. Send `appointment_booked`, `appointment_completed`, `appointment_no_show`. Automate reminders before, review requests after, and re-booking nudges for lapsed clients. Your backend forwards checkout and order activity as events, and creates contacts at signup with proper consent flags. Note that the deep cart-recovery integration (recovery URLs, store-generated coupons) is built around the native [Shopify](/integrations/shopify) and [WooCommerce](/integrations/woocommerce) connections. ## Checklist before production * API key stored server-side in a secrets manager — never in client code. * Retry-with-backoff on `429`/`5xx` — see [Rate limits](/developers/rate-limits) and [Errors](/developers/errors). * Consent (`email_opt_in`) set honestly at contact creation — see [Contacts API](/developers/api/contacts). * Event names stable and documented for the marketing team. * A staging organization for testing, so test events never trigger production automations. # Errors Source: https://help.retainful.com/developers/errors HTTP status codes, error response shape, and how to handle each failure mode. The API uses conventional HTTP status codes. Anything in the `2xx` range succeeded; `4xx` means the request needs fixing on your side; `5xx` means something failed on Retainful's side. ## Status codes | Code | Meaning | What to do | | ----- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | `200` | Success. Returned by reads and by [adding a contact to a list](/developers/api/contact-groups). | Carry on. | | `201` | Created. Returned by [contact creation](/developers/api/contacts) and [events](/developers/api/events). | Carry on. | | `400` | Bad request — malformed JSON, failed validation, or an unrecognized field. | Read `message`; fix the payload. Don't retry unchanged. | | `401` | Missing or invalid API key. | Check the header, and that the key hasn't been revoked or expired. | | `404` | Resource not found. | Check the ID; the resource may have been deleted. | | `429` | Rate limited. | Wait for the window named in `Retry-After-*` — see [Rate limits](/developers/rate-limits). | | `5xx` | Server error. | Retry with exponential backoff; if persistent, contact support. | Validation failures return `400`, not `422`. There is no `403` — an API key either authenticates or it doesn't. ## Error response shape Every error carries the same five fields: ```json 401 example theme={null} { "statusCode": 401, "timestamp": "2026-06-11T12:00:00.000Z", "path": "/api/v1/events", "method": "POST", "message": "Invalid or expired API key" } ``` Errors you can act on programmatically add three more — `code`, `title`, and `detail`. `message` and `detail` carry the same text: ```json 400 example theme={null} { "statusCode": 400, "timestamp": "2026-06-11T12:00:00.000Z", "path": "/api/v1/customer/create", "method": "POST", "message": "Email must be a valid email address", "code": "VALIDATION_ERROR", "title": "Invalid request payload", "detail": "Email must be a valid email address" } ``` Branch on `code` when it's present. Note that `401`, `404`, and `429` responses have **no** `code` field — check for its presence before reading it. ## Error codes | Code | Meaning | | ----------------------------- | --------------------------------------------------------------------- | | `VALIDATION_ERROR` | A field is missing, the wrong type, or not a recognized field name. | | `EVENT_NAME_REQUIRED` | `eventName` was empty. | | `CONTACT_REQUIRED` | An [event](/developers/api/events) was sent with no `contact` object. | | `CONTACT_IDENTIFIER_REQUIRED` | The `contact` object had no `contactUUID`, `email`, or `phone`. | | `CONTACT_NOT_FOUND` | The `contactUUID` sent with an event matches no contact. | `message` names only the **first** field that failed validation. If several fields are wrong, you'll fix them one round-trip at a time — validate on your side before sending. ## Retry guidance * **Retry**: `429` (after the `Retry-After-*` window) and `5xx` (with exponential backoff and a retry cap). * **Don't retry unchanged**: `400`, `401`, `404` — the same request fails the same way. * **Retries are safe by design.** Contact creation upserts, so repeating it is harmless. Events are deduplicated on [`uniqueIdentifier`](/developers/api/events#idempotency) — a retried event updates the stored record instead of triggering the automation twice. Reuse the same identifier when retrying, and only change it for a genuinely new occurrence. Log the full response body on failures, not just the status code — `message` almost always names the exact field that failed. # Event reference Source: https://help.retainful.com/developers/event-types The events flowing through Retainful — from your store, from email engagement, and from your own systems. Everything in Retainful runs on events. This page catalogs where they come from and what they're used for — useful when deciding what can trigger an [automation](/automations/triggers) or feed a [segment](/audience/segments). ## Store events Delivered automatically by your store connection ([Shopify](/integrations/shopify) webhooks or the [WooCommerce](/integrations/woocommerce) plugin): | Event | Source | Notes | | ----------------------------------- | --------------------- | ------------------------------------------------------------------ | | Checkout started / updated | Both | Carries cart contents and the recovery URL — powers cart recovery. | | Order placed | Both | New order with line items, totals, discount codes. | | Order paid | Both | Payment confirmed. | | Order fulfilled | Both | Shipment created. | | Order cancelled / refunded | Both | Reversals — used to keep attribution honest. | | Customer created / updated | Both | Syncs the contact profile. | | Product created / updated / deleted | Both | Keeps the catalog fresh for email product blocks. | | Product viewed | Both (pixel / plugin) | Browsing signal for targeting. | | Back in stock / out of stock | WooCommerce | Inventory transitions for back-in-stock flows. | ## Engagement events Generated by Retainful's own sending pipeline: | Event | Meaning | | ---------------- | --------------------------------------------------------------------------------------------------- | | Email delivered | Accepted by the recipient's mail server. | | Email opened | Tracking pixel fired. | | Email clicked | A tracked link was followed. | | Email bounced | Hard or soft delivery failure — hard bounces [suppress](/audience/suppressed-contacts) the contact. | | Email complained | Marked as spam — suppresses the contact. | | Unsubscribed | The contact opted out. | These power engagement-based triggers ("email opened", "email clicked") and segment conditions ("hasn't opened in 60 days"). ## Retainful events | Event | Meaning | | --------------------------------- | --------------------------------------------------- | | Subscribed to list | Contact joined a list — the welcome-series trigger. | | Subscribed to email marketing | Contact gained marketing consent. | | Unsubscribed from email marketing | Consent withdrawn. | ## Custom events Anything you send through the [Events API](/developers/api/events) — your event types appear alongside the built-ins in the automation builder, with their payload fields available to filters and personalization. There is no fixed list: any `eventName` you send is valid. The name is normalized and prefixed with your integration's key to form the event type you'll pick in the trigger list: | `eventName` you send | Appears as | | -------------------- | ----------------------------- | | `Add to Cart` | `REST-API.ADD_TO_CART` | | `Checkout Started` | `REST-API.CHECKOUT_STARTED` | | `Purchase Completed` | `REST-API.PURCHASE_COMPLETED` | | `Placed Order` | `REST-API.PLACED_ORDER` | These custom commerce events are distinct from the [store events](#store-events) above. If your store is connected via Shopify or WooCommerce, those events already arrive automatically — you don't need to send them through the API as well. The REST API events are for storefronts and systems Retainful doesn't integrate with directly. An event type only appears in the trigger picker once the first event of that type has been received, or after you call `POST /api/v1/events/register`. See [Register an event schema](/developers/api/events#register-an-event-schema). ## Event data lifecycle 1. An event arrives (store webhook, engagement tracker, or your API call). 2. It's validated, deduplicated, and matched to a contact (creating one when appropriate). 3. It's recorded on the contact's [timeline](/audience/contacts). 4. Matching [automation triggers](/automations/triggers) fire. 5. Segments referencing the event re-evaluate. Processing is near-real-time — an abandoned checkout can enter a recovery flow within seconds of the event arriving. # Developer overview Source: https://help.retainful.com/developers/overview Build on Retainful: REST API, custom events, webhooks, and storefront tracking. The Retainful API lets your systems talk to Retainful directly — create contacts, send custom events that trigger automations, and manage list membership. If you run a custom storefront, a subscription service, or any backend with customer activity, this is how you plug it in. ## What you can build Create and update contact profiles from your own systems — signups, profile changes, consent updates. Track anything — bookings, renewals, quiz results — and use it to trigger automations and build segments. Add and remove contacts from lists programmatically. Get an HTTP call to your endpoint when a contact reaches a webhook step in an automation. ## How the pieces fit ```text theme={null} Your system ──POST /events──▶ Retainful ──triggers──▶ Automations ──▶ Email / WhatsApp │ │ ▼ ▼ Segments ◀── profiles Webhook step ──▶ your endpoint ``` 1. **Authenticate** every request with an API key — see [Authentication](/developers/authentication). 2. **Create contacts** or let events create them implicitly. 3. **Send events** as things happen. Events appear on contact timelines, are available to segments, and can [trigger automations](/automations/triggers#custom-events). 4. **Receive webhooks** from automation flows to close the loop into your own systems. ## Conventions * **Format** — JSON in, JSON out. Most responses use a JSON:API-style envelope: a `data` object with `type`, `id`, `attributes`, and `links`. [Adding a contact to a list](/developers/api/contact-groups) is the exception and returns a plain object. * **Field names** — snake\_case (`first_name`, not `firstName`). Unrecognized fields are rejected with `400` rather than ignored. * **IDs** — resources are identified by stable unique IDs; contacts can also carry your own `external_id`. * **Errors** — standard HTTP status codes with a JSON body; see [Errors](/developers/errors). * **Rate limits** — 10/second, 100/minute, 1,000/hour, counted per IP; see [Rate limits](/developers/rate-limits). ## Quick example ```bash Track an event theme={null} curl -X POST "https://api.retainful.net/api/v1/events" \ -H "Content-Type: application/json" \ -H "X-API-Key: $RETAINFUL_API_KEY" \ -d '{ "eventName": "Appointment Booked", "uniqueIdentifier": "booking_44812", "contact": { "email": "jane@example.com" }, "eventData": { "service": "Consultation", "date": "2026-07-01" } }' ``` That single call creates the contact if needed, records the event on her timeline, and fires any automation triggered by `Appointment Booked`. The `uniqueIdentifier` makes it safe to retry — see [Idempotency](/developers/api/events#idempotency). The base URL for API requests is shown alongside your API key when you create it in **Integrations**. # Rate limits Source: https://help.retainful.com/developers/rate-limits Per-IP request limits across three windows, the headers that report them, and how to back off correctly. API requests are rate-limited. Three windows apply simultaneously, and the same limits apply to every endpoint — reads and writes alike. ## The limits | Window | Limit | | ---------- | -------------- | | Per second | 10 requests | | Per minute | 100 requests | | Per hour | 1,000 requests | Exceeding any one of them returns `429 Too Many Requests`. Limits are counted **per client IP address**, not per API key. If several of your systems share an outbound IP, they share a single budget. Conversely, spreading traffic across hosts gives each its own. ## Rate limit headers Every response reports your standing in all three windows. Header names carry the window as a suffix: | Header | Meaning | | ----------------------------------------- | ----------------------------------------------------------------------------- | | `X-RateLimit-Limit-short` | Requests allowed in the 1-second window. | | `X-RateLimit-Remaining-short` | Requests left in it. | | `X-RateLimit-Reset-short` | When it resets. | | `X-RateLimit-Limit-medium` | The same three, for the 1-minute window. | | `X-RateLimit-Limit-long` | The same three, for the 1-hour window. | | `Retry-After-short` / `-medium` / `-long` | On a `429` — how long to wait. The suffix tells you which window you tripped. | The suffix matters. There is no unsuffixed `X-RateLimit-Remaining` or `Retry-After` — reading those returns nothing. Parse the suffixed names. ## When you exceed the limit You receive `429` with a body explaining the failure. Wait for the window named in the `Retry-After-*` header, then resume. ```python Exponential backoff theme={null} import time, requests RETRY_AFTER_HEADERS = ("Retry-After-short", "Retry-After-medium", "Retry-After-long") def post_with_backoff(url, json, headers, max_attempts=5): for attempt in range(max_attempts): resp = requests.post(url, json=json, headers=headers) if resp.status_code != 429: return resp wait = next( (float(resp.headers[h]) for h in RETRY_AFTER_HEADERS if h in resp.headers), 2 ** attempt, ) time.sleep(wait) raise RuntimeError("rate limited after retries") ``` ## Designing within the limits * **Don't sync by polling.** Instead of re-fetching all contacts hourly, push changes as they happen with [events](/developers/api/events). * **Mind the hourly ceiling.** 1,000 requests per hour is the binding constraint for backfills — averaging under one call every 3.6 seconds. A job that respects the per-second limit can still exhaust the hour. * **Batch your backfills.** For large one-time contact imports, the dashboard's [CSV import](/audience/import-contacts) avoids the API entirely and is the better tool. * **Queue on your side.** Put API calls on a job queue with retry and backoff rather than calling inline from request handlers. * **Watch `X-RateLimit-Remaining-long`** and slow down *before* it reaches zero — it beats reacting to `429`s. # Storefront tracking Source: https://help.retainful.com/developers/web-tracking How Retainful tracks on-site behavior — the Shopify web pixel, the WooCommerce plugin, and what data is collected. Retainful's storefront tracking records what shoppers do **before** they identify themselves — product views, sessions, checkout starts — and stitches it to a contact as soon as an email appears (at checkout or in a signup form). ## How tracking is delivered Retainful registers a **web pixel** through Shopify's pixel manager — a sandboxed script Shopify runs on every storefront page, including checkout. No theme edits, and it survives theme changes. What it captures: * **Product views** — product, variant, price, and URL for each view. * **Checkout contact info** — the email/phone a shopper enters at checkout, with their stated marketing consent. * **Session continuity** — a short-lived session identifier so a later checkout can be connected to the browsing that preceded it. Enable or disable it from **Integrations → Shopify → Web Pixel**. The [Retainful plugin](/integrations/woocommerce) tracks server-side from inside WordPress — product views, checkout starts, and order events are posted directly from your site to Retainful's event endpoints. Nothing extra to install on the storefront. ## What the data powers | Captured signal | What it enables | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | | Product views | Browse-based segments and recently-viewed product blocks in emails. | | Checkout email + consent | [Cart recovery](/automations/abandoned-cart-recovery) reaching shoppers who never finished checkout — with consent respected. | | Sessions | Connecting anonymous browsing to the contact once they identify. | | Form submissions | New subscribers flowing into lists, triggering welcome automations. | ## Privacy posture * Tracking is **first-party** to your store relationship: data goes to your Retainful organization and is used for your marketing — it is not pooled across stores. * Consent captured at checkout (email/SMS opt-in checkboxes) is stored on the contact and **enforced at send time** — see [subscription status](/audience/overview#subscription-status). * Contacts can be deleted entirely (dashboard or API) to honor GDPR/CCPA erasure requests. If you run a consent management platform (cookie banner), treat Retainful's storefront tracking like your other marketing tags and gate it per your regional requirements. # Webhooks Source: https://help.retainful.com/developers/webhooks Receive HTTP calls from automation flows into your own systems. The **Webhook step** in an [automation](/automations/steps) sends an HTTP request to a URL you control whenever a contact reaches that point in the flow. It's the bridge from Retainful's marketing logic back into your stack. ## What you can do with it * Notify your backend when a customer enters a win-back flow, so support sees it in your CRM. * Post to a Slack webhook when a VIP segment member abandons a large cart. * Trigger a fulfillment-side action — add a gift, flag an account — when a flow milestone is reached. * Feed your data warehouse with flow progress, joined later against revenue. ## Configure a webhook step 1. In the automation canvas, add a **Webhook** step where you want the call to fire. 2. Enter the **URL** of your endpoint (HTTPS). 3. The step sends the contact and event context as a JSON `POST` body — the same data available to that point of the flow (contact profile fields, trigger event payload). ## Building a reliable receiver ```javascript Express example theme={null} app.post("/hooks/retainful", (req, res) => { // 1. Acknowledge fast — do the real work async res.status(200).end(); // 2. Process from a queue, not inline queue.add("retainful-webhook", req.body); }); ``` * **Respond quickly with a 2xx.** Do slow work (database writes, third-party calls) after acknowledging, not before. * **Expect retries and duplicates.** Network blips can cause re-delivery — make your handler idempotent (dedupe on contact + step + timestamp). * **Validate what you receive.** Treat the payload as untrusted input; check the fields you depend on exist before using them. * **Keep the URL secret-ish.** Use an unguessable path or a query token (`/hooks/retainful?token=...`) and reject calls without it. ## Inbound webhooks (into Retainful) Going the other direction — your systems telling Retainful something happened — isn't a webhook you configure; it's the [Events API](/developers/api/events). Store webhooks from Shopify and WooCommerce are managed automatically by the [integrations](/integrations/overview) and need no setup from you. # Deliverability best practices Source: https://help.retainful.com/email-setup/deliverability How to keep your emails landing in the inbox — authentication, list hygiene, engagement, and content. Deliverability is whether your emails reach the inbox or the spam folder. It's earned over time — mailbox providers score every sender on authentication, list quality, and how recipients react to their mail. Retainful automates much of this (suppression, unsubscribe handling, bounce processing), but the practices below are yours to own. ## 1. Authenticate your domain Non-negotiable. Verify your [sending domain](/email-setup/sending-domains) with DKIM, SPF, and DMARC before doing any meaningful volume. Gmail and Yahoo require authentication from bulk senders — without it, your mail may be rejected outright. ## 2. Only email people who opted in Every spam complaint hurts every future send. The complaint threshold mailbox providers tolerate is roughly **0.1–0.3%** — one or two complaints per thousand emails. * Never buy, rent, or scrape lists. * Use [double opt-in](/audience/contacts#single-vs-double-opt-in) where quality matters more than volume. * Re-permission old lists before emailing them after a long gap. ## 3. Keep your list clean * Retainful [suppresses](/audience/suppressed-contacts) hard bounces, complaints, and unsubscribes automatically — don't fight it. * Watch bounce rates per campaign; above \~2% means stale data. * Periodically sunset contacts who haven't opened or clicked in 6–12 months — emailing the permanently disengaged drags down your reputation with everyone else. ## 4. Send relevant mail at a steady rhythm * **Segment** instead of blasting — see [Segments](/audience/segments). Engagement (opens, clicks, replies) is the strongest positive signal a sender can earn. * Keep a consistent cadence; long silences followed by sudden bursts look like spam patterns. * If you're moving to Retainful with a large list, warm up — start with your most engaged segment and widen over a couple of weeks. ## 5. Mind your content Content matters less than reputation, but it still matters: * Avoid ALL-CAPS subjects, excessive punctuation!!!, and walls of images with no text. * Keep your unsubscribe link obvious. Making it hard to leave converts unsubscribes into spam complaints — a far worse outcome. * Send a test to yourself and check where it lands before every major campaign. ## 6. Monitor Your [campaign analytics](/campaigns/analytics) tell the story: delivery rate sliding, opens collapsing on a specific mailbox provider, or bounce spikes are all early warnings. Catch them early and the fix is a cleanup; catch them late and it's a slow reputation rebuild. The compounding rule of deliverability: every send to an engaged subscriber makes the next send easier, and every send to a dead address makes it harder. Optimizing list quality beats optimizing send volume, every time. # Sender addresses Source: https://help.retainful.com/email-setup/sender-addresses Set up verified from and reply-to addresses for your campaigns and automations. Sender addresses are the identities your emails are sent from. Every campaign and automation email picks one of your verified senders. ## From address vs. reply-to * **From address** — what recipients see as the sender (`Maya from Acme `). It should be on your [verified sending domain](/email-setup/sending-domains). * **Reply-to address** — where replies go, if different from the from address. Useful when you send from `hello@` but want replies in your support inbox. ## Add a sender address Go to **Settings → Email → From Addresses** and click **Add address**. Enter the email and the **from name** customers will see. Retainful sends a verification email to that address. Click the link inside. Only verified addresses can be used in campaigns and automations. Reply-to addresses are managed the same way under **Settings → Email → Reply Addresses**. ## Choosing a good from name The from name is the most visible thing in the inbox — often more than the subject line. * **"Maya from Acme"** — personal and branded; a strong default. * **"Acme"** — clean and recognizable. * Avoid **"noreply@…"** — it tells customers you don't want to hear from them, and replies are a positive deliverability signal you'd be giving up. Don't send from a free mailbox domain like `@gmail.com`. DMARC policies on those domains cause your messages to fail authentication when sent through any email platform. Always use an address on your own domain. ## Blocked domains Under **Settings → Email → Blocked Domains** you can maintain a list of recipient domains Retainful should never send to — useful for blocking known spam-trap domains or a partner's domain that asked to be excluded. # Sending domains Source: https://help.retainful.com/email-setup/sending-domains Verify your own domain with DKIM, SPF, DMARC, and return-path records so your emails reach the inbox. A sending domain lets Retainful send email **as you** — from `hello@mystore.com` instead of a shared address. Mailbox providers like Gmail and Yahoo now effectively require this authentication for bulk senders, and it's the single biggest deliverability upgrade you can make. ## What the DNS records do You don't need to understand these deeply — but here's what each record proves: | Record | What it proves | | --------------- | ------------------------------------------------------------------------------------------------------------------ | | **DKIM** | The email genuinely came from your domain and wasn't altered in transit (a cryptographic signature). | | **SPF** | Retainful's mail servers are allowed to send for your domain. | | **DMARC** | Tells receivers what to do with mail that fails the checks — and that you take authentication seriously. | | **Return-path** | Bounces come back to Retainful so invalid addresses get [suppressed](/audience/suppressed-contacts) automatically. | ## Add and verify your domain Go to **Settings → Email → Domains** and click **Add domain**. Enter the domain you send from — typically your store's domain, like `mystore.com`. Retainful displays the exact records to create — each with a **type** (TXT or CNAME), a **host/name**, and a **value**. The records are split into sending and receiving sections; you need all of them. Log in to wherever your domain's DNS lives — Cloudflare, GoDaddy, Namecheap, your registrar — and create each record. Copy-paste the values exactly; a single missing character fails verification. Back in Retainful, click **Verify** for each record. Verification re-checks DNS, so you can retry as often as you like. DNS changes can take minutes to a few hours to propagate worldwide. If verification fails right after you added the records, wait an hour and try again before changing anything. ## Common DNS pitfalls Some DNS providers add `.mystore.com` to whatever you enter. If Retainful asks for host `rtnf._domainkey.mystore.com`, you may need to enter just `rtnf._domainkey`. If verification fails, check the record with a DNS lookup tool to see what actually got created. CNAME records used for email must be **DNS only** (gray cloud), not proxied (orange cloud). Toggle the proxy off for those records. A domain can only have **one** SPF record. Don't add a second — merge Retainful's include into your existing record (for example `v=spf1 include:existing.com include:mailgun.org ~all`). Compare each record character by character — trailing spaces and smart quotes from copy-paste are the usual culprits. Make sure the records are on the exact domain you added (not `www.`). ## After verification Add the addresses you'll send from on that domain under **Settings → Email → From Addresses** — see [Sender addresses](/email-setup/sender-addresses). Then send yourself a test campaign and check it lands in the inbox, not spam. # Shortcodes Source: https://help.retainful.com/email-setup/shortcodes The full reference for the shortcodes that personalize your emails — contact details, store info, cart links, coupons, and order summaries. Shortcodes personalize your emails. Write `{{ contact.firstName }}` once, and every recipient sees their own name. ```liquid theme={null} Hi {{ contact.firstName | default: 'there' }}, thanks for shopping with {{ shop_name }}! ``` Renders as: **Hi Ada, thanks for shopping with Acme Supply Co!** This page is the full syntax reference. To insert a tag from the editor toolbar instead of writing it by hand, see [Personalization](/campaigns/personalization). ## Where each group works Not every shortcode works in every email. Using one outside its context leaves it blank rather than raising an error. | Group | Broadcasts | Automations | | ------------------------------------- | ---------------------------- | ----------------------------- | | [Customer details](#customer-details) | Yes | Yes | | [Store](#store) | Body only, not subject lines | Yes | | [Abandoned cart](#abandoned-cart) | No | Abandoned-cart flows only | | [Coupon](#coupon) | No | Flows with a coupon step only | | [Summary block](#summary-block) | No | Yes | | [Unsubscribe](#unsubscribe) | Yes | Yes | ## Customer details Works in every email — broadcasts and automations. | Shortcode | Shows | Example | | -------------------------- | ----------------- | ------------------- | | `{{ contact.firstName }}` | First name | `Ada` | | `{{ contact.lastName }}` | Last name | `Lovelace` | | `{{ contact.fullName }}` | Full name | `Ada Lovelace` | | `{{ contact.email }}` | Email address | `ada@example.com` | | `{{ contact.phone }}` | Phone number | `+1 (555) 123-4567` | | `{{ contact.address1 }}` | Address line 1 | `123 Market St` | | `{{ contact.address2 }}` | Address line 2 | `Apt 4B` | | `{{ contact.city }}` | City | `London` | | `{{ contact.state }}` | State / province | `Greater London` | | `{{ contact.postalCode }}` | ZIP / postal code | `12345` | | `{{ contact.country }}` | Country | `United Kingdom` | ```liquid theme={null} Hi {{ contact.firstName | default: 'there' }}, ``` Renders as `Hi Ada,` — or `Hi there,` for a contact with no first name. * **Always add `| default:`.** Most contacts have only an email address, so without a fallback your email opens `Hi ,`. * **Always write `contact.` in front.** `{{ firstName }}` on its own silently fails in broadcast emails. * `{{ contact.fullName }}` shows the **email address** when both names are blank. For greetings, `{{ contact.firstName | default: 'there' }}` is safer. ## Store Works in every email. | Shortcode | Shows | Example | | ------------------------------------------- | ------------------------------- | ---------------------------------- | | `{{ shop_name }}` | Your store name | `Acme Supply Co` | | `{{ shop_email }}` | Your store email | `orders@acme.com` | | `{{ shop_address }}` | Your store address | `123 Market St` | | `{{ retainful_shop_url }}` | Link to your store | `https://acme.com` | | `{{ retainful_shop_url_with_coupon_code }}` | Store link, coupon auto-applied | `https://acme.com/discount/SAVE10` | ```liquid theme={null} Thanks for shopping with {{ shop_name }}! Visit our store ``` * **In broadcasts these work in the email body but not in the subject line.** In automations they work in both. * `{{ retainful_shop_url_with_coupon_code }}` requires a coupon step in the workflow. In a broadcast it drops the coupon silently and links to your store. ## Abandoned cart Abandoned-cart automations only. In any other email these render blank. | Shortcode | Shows | | ----------------------------------------------- | ------------------------------ | | `{{ abandoned_checkout_url }}` | Link back to their cart | | `{{ abandoned_checkout_url_with_coupon_code }}` | Cart link, coupon auto-applied | ```liquid theme={null} {% if abandoned_checkout_url.size > 0 %} Complete your order {% endif %} ``` * **Keep the `.size > 0` check.** Without it, an email with no cart still shows the button, linking nowhere. See [Hide empty values correctly](#hide-empty-values-correctly). * The coupon version also requires a coupon step in the workflow. ## Coupon Automations with a coupon step only. These never work in broadcasts. | Shortcode | Shows | Example | | ------------------------------------ | -------------------- | --------------------------------- | | `{{ coupon.code }}` | The discount code | `SAVE10XYZAB` | | `{{ retainful_coupon_expiry_date }}` | When it expires | Use with the `date` filter, below | | `{{ coupon.expiration_text }}` | How long it is valid | `7 days` | | `{{ coupon.expiration_days }}` | Days until expiry | `7` | ```liquid theme={null} {% if coupon.code.size > 0 %} Your code: {{ coupon.code }} Valid for {{ coupon.expiration_text }}. Expires {{ retainful_coupon_expiry_date | date: "%B %d, %Y" }} {% endif %} ``` Renders as: Your code: `SAVE10XYZAB`. Valid for 7 days. Expires March 05, 2026 * Each contact receives their own unique code. * **Always apply `| date:` to `{{ retainful_coupon_expiry_date }}`.** On its own it prints a raw timestamp. * The expiry clock starts when the coupon is **created**, so every email in a sequence shows the same date. * **Keep the `.size > 0` check.** With no coupon step, `{{ coupon.expiration_days }}` prints `0` and the rest print blank, so the email reads "Valid for . (0 days)". ## Summary block Lists the items your customer ordered or left in their cart. Drag the block in and the editor writes the loop for you — you only edit what is inside it. Automation templates only. The Summary block is not available in broadcast campaigns. | Field | Shows | Example | | ---------------------- | ----------------------------- | ----------------------- | | `{{ item.name }}` | Product name | `Classic White T-Shirt` | | `{{ item.quantity }}` | Quantity | `2` | | `{{ item.price }}` | Line total (price × quantity) | `$59.98` | | `{{ item.unitPrice }}` | Price for one | `$29.99` | | `{{ item.imageUrl }}` | Product image | `https://.../shirt.jpg` | Stripped of its layout tables and Outlook-only comments, a default Summary block is: ```liquid theme={null} {% for item in event.line_items limit:9999 %}
{{item.name}} × {{item.quantity}}
{{item.price}}
{% endfor %} ``` This produces one entry per item: an image, then `Classic White T-Shirt × 2`, then `$59.98`. `{{ item.price }}` is already the line total — never multiply it. `{{ item.price | times: item.quantity }}` counts the quantity twice: a 2 × `$29.99` line shows `$119.96` instead of `$59.98`. For "unit × qty = total", write `{{ item.unitPrice }} × {{ item.quantity }} = {{ item.price }}`. * **Show or hide the name and price**, and set their colour and size, in the block's settings panel. You do not need to touch the shortcodes. * **The Review block is the same thing with a button**, but its loop variable is `line`, not `item` — write `{{ line.name }}` there. * **If you edit the source:** keep the `htmlmin:ignore` HTML comments shown above (they stop the loop tags being mangled), do not rename `item`, and do not change `event.line_items`. `limit:9999` means no limit — lower it to cap how many items show. * **In preview it shows placeholder text** — `Product Title`, `Quantity`, `$xx.xx`, and a grey image. Real values appear on send. ## Unsubscribe Works in every email. | Shortcode | Shows | | ------------------------------------- | ------------------------- | | `{{ unsubscribe_shop_customer_url }}` | Personal unsubscribe link | ```liquid theme={null} Unsubscribe ``` Unique per contact, and legally required in marketing email. If your email has no unsubscribe link, Retainful adds one automatically — but placing it yourself looks better. See [Deliverability best practices](/email-setup/deliverability). ## Four rules worth knowing ### Always add a fallback ```liquid theme={null} Hi {{ contact.firstName | default: 'there' }}, ``` `default` covers every "no value" case — blank, never set, or missing. | Contact's first name | Output | | -------------------- | --------- | | `Ada` | Hi Ada, | | *(blank)* | Hi there, | | *(never set)* | Hi there, | ### Hide empty values correctly To hide a block when a shortcode has no value, test `.size > 0`: **Correct:** ```liquid theme={null} {% if coupon.code.size > 0 %}...{% endif %} ``` **Incorrect — the block always shows:** ```liquid theme={null} {% if coupon.code %}...{% endif %} ``` The plain form looks right but does not work. An empty value is still "something" as far as Liquid is concerned, so the block renders anyway, with a blank inside it. That is what produces an anchor tag with an empty `href` — a real button that goes nowhere. `.size > 0` is safe on every shortcode in this document. ### Typos are invisible `{{ contact.frstName }}` renders as empty space. No error, no warning. Proofread, and always send a test email. A test proves your *spelling* is right, not that the data will *be there*. A test send fills in sample values even for shortcodes that come back blank on a real send. ### Do not invent tags Only use what is in this document. A made-up `{% tag %}` — such as `{% current_year %}` — **breaks the whole email**: every shortcode disappears and the raw tag is shown to your customer. For the current year, use `{{ "now" | date: "%Y" }}`. # Form analytics Source: https://help.retainful.com/forms/analytics Measure views, submissions, and conversion rate for every signup form. Open any form and switch to its **Analytics** view to see how it's performing. ## The metrics | Metric | What it means | | ------------------- | -------------------------------------------- | | **Views** | How many times the form was displayed. | | **Submissions** | How many visitors signed up. | | **Conversion rate** | Submissions ÷ views. The number to optimize. | A well-targeted popup with an incentive typically converts **3–8%** of views. Embedded forms convert less per view but show constantly, so judge them by total submissions. ## Who signed up The analytics view lists the contacts captured by this form, so you can verify data is landing where you expect — each contact's profile also records the form as their source in the [activity timeline](/audience/contacts). ## Improving conversion The offer or the ask is the problem. Lead the heading with the value ("Get 10% off") rather than the mechanism ("Subscribe"), cut every field you don't truly need, and consider adding a discount if there isn't one. A targeting issue, not a design issue. Check the [display rules](/forms/display-rules) — a 60-second delay or 80% scroll trigger means most visitors never see it. On Shopify, also confirm the [theme app extension](/integrations/shopify#theme-app-extension) is enabled. If signups never engage with your emails, enable [double opt-in](/audience/contacts#single-vs-double-opt-in) — you'll get fewer but real subscribers — and make the welcome email arrive instantly via a list-triggered automation. The biggest lever is usually the incentive. "10% off your first order" against "join our newsletter" is rarely a close contest — test it for a week and compare conversion rates. # Create a form Source: https://help.retainful.com/forms/create-a-form Design a signup form, attach an incentive, and publish it to your storefront. ## 1. Start from a template Go to **Signup Forms** and click **Create form**. Pick a template that matches the type you want — popup, exit intent, add-to-cart, or embedded. Everything is editable afterwards. ## 2. Design it The form editor has three tabs: ### Design Visual styling — colors, fonts (including any Google Font), buttons, spacing, and an optional image. The same controls style the **teaser** (the small collapsed tab that stays visible after a visitor dismisses the popup) and the coupon display. A custom CSS box is there if you want fine-grained control, but most forms never need it. ### Content What the form says and collects: * **Heading and description** — lead with the value: "Get 10% off your first order" beats "Subscribe to our newsletter". * **Fields** — email is the core; you can also collect names, phone, or answers stored in [custom fields](/audience/custom-fields). Every extra field lowers conversion, so ask only for what you'll use. * **Discount** — optionally attach a coupon that's revealed after signup. Welcome discounts routinely double signup rates. * **Custom HTML** — for anything the standard blocks don't cover. ### Rules When and where the form appears — covered in [Display rules](/forms/display-rules). ## 3. The three phases A form is really three screens: 1. **Signup phase** — the ask: heading, fields, button. 2. **Success phase** — the thank-you, and the coupon reveal if you attached one. 3. **Teaser** — the collapsed tab shown before opening or after dismissal, so interested visitors can reopen the form anytime. Preview each phase from the editor before publishing. ## 4. Connect the list Choose which [list](/audience/lists) new signups join. If you have a welcome automation triggered by that list, new subscribers enter it automatically — this is the recommended setup. ## 5. Review and publish The review step checks everything's in place; then click **Publish**. The form goes live on your storefront within a few minutes. Visit your store in a private browser window to see the form as a new visitor would. (Forms typically don't re-show to people who already subscribed or dismissed them — a fresh session shows the true experience.) ## Pause or edit anytime Forms can be **paused** (hidden from your store) and resumed without losing their design or stats. Edits to a published form go live on your storefront automatically. # Display rules Source: https://help.retainful.com/forms/display-rules Control when your form appears, on which pages, and how often — so it converts without annoying anyone. A form that pops up instantly on every page drives people away. Display rules let you show the right form at the right moment. ## Timing triggers | Trigger | The form appears… | Good default | | ---------------- | ----------------------------------------------- | ------------ | | **Time on page** | After the visitor has been on the page a while. | 5–10 seconds | | **Scroll depth** | After scrolling a percentage of the page. | 30–50% | | **Exit intent** | When the cursor moves to leave the window. | — | | **Add to cart** | When an item is added to the cart. | — | Time-on-page and scroll triggers both signal *engagement* — the visitor has shown interest before you interrupt. Instant popups convert worse and annoy more. ## Frequency Control how often the same visitor sees the form: * After a visitor **dismisses** the form, it collapses into the teaser instead of reappearing on every page view. * After someone **subscribes**, the form stops showing them entirely. This is why testing your own form is best done in a private browser window — your normal browser remembers you dismissed it. ## The teaser The teaser is the small tab (for example, "Get 10% off") that stays at the edge of the screen. It keeps your offer one click away without covering content — visitors who change their mind can reopen the form anytime. Style it from the **Design** tab; its text is set in **Content**. ## Choosing rules per form type * **Welcome popup** — time on page 5–10s. New visitors see the offer after they've engaged. * **Exit intent** — exit trigger, with a slightly stronger hook ("Wait — here's 10% off"). * **Add-to-cart form** — the add-to-cart trigger; capturing email here means [cart recovery](/automations/abandoned-cart-recovery) can reach this shopper even if they never start checkout. * **Embedded form** — no trigger needed; it renders wherever you place it. ## Running multiple forms You can run several forms at once — say, an exit-intent popup plus an embedded footer form. Avoid running two popups with overlapping triggers on the same pages; if a visitor qualifies for both, one experience should clearly win. # Signup forms overview Source: https://help.retainful.com/forms/overview Grow your email list with popups, exit-intent forms, and embedded signup forms on your storefront. Signup forms turn anonymous store visitors into subscribers you can market to. Retainful's form builder creates popups and embedded forms that run on your storefront — no code required. ## Form types | Type | How it appears | Best for | | --------------- | -------------------------------------------------------------- | ------------------------------------------------------ | | **Popup** | Opens over the page after a trigger you choose (time, scroll). | General list growth with a welcome offer. | | **Exit intent** | Appears when the visitor moves to leave the page. | A last-chance offer before they go. | | **Add to cart** | Appears when a shopper adds an item to their cart. | Capturing email early so cart recovery can reach them. | | **Embedded** | Sits inline in your page content — footer, blog, landing page. | Always-available signup without interrupting anyone. | ## How a form grows your list 1. A visitor sees your form and enters their email (and anything else you ask). 2. They're created as a contact and added to the [list](/audience/lists) you chose — with [double opt-in](/audience/contacts#single-vs-double-opt-in) if you've enabled it. 3. If the form offers a discount, they see their coupon right away. 4. Joining the list can [trigger your welcome automation](/automations/triggers) — so the relationship starts immediately. ## Requirements Forms display on your storefront through your store connection: * **Shopify** — enable the [theme app extension](/integrations/shopify#theme-app-extension). If a form isn't appearing, the popup requirements check on the Shopify integration page tells you what's missing. * **WooCommerce** — forms are served through the [Retainful plugin](/integrations/woocommerce). ## Guides Pick a template, design it, connect a list. The forms worth building first — and the offer that makes each one convert. Control when, where, and to whom your form appears. Views, submissions, and conversion rate. # Form use cases Source: https://help.retainful.com/forms/use-cases The signup forms worth building first — what each form type is best at, and the offer that makes it convert. A signup form is only as good as the moment it appears and the reason it gives someone to enter their email. Each [form type](/forms/overview#form-types) is built for a different moment in the visit. Here are the most popular use cases, the type to reach for, and how to set each one up. Whatever you build, connect it to a [list](/audience/lists) that triggers a [welcome series](/automations/use-cases/welcome-series). Capturing the email is half the job — the automation turns it into a sale. ## Welcome discount popup **The bread-and-butter list builder.** A timed popup offers first-time visitors a discount in exchange for their email — the highest-volume way most stores grow their list. | | | | ----------------- | ------------------------------------------------------------- | | **Form type** | Welcome Popup | | **When it shows** | A few seconds after landing, or after scrolling part-way down | | **The offer** | "Get 10% off your first order" + a coupon revealed on signup | In **Signup Forms → Create form**, pick the **Welcome Popup** type. Headline the *benefit* — "Get 10% off your first order" converts far better than "Subscribe to our newsletter." Ask for email only; every extra field lowers signups. Reveal a coupon on the success screen. A welcome discount routinely doubles signup rates. Show after \~5 seconds or at 30–50% scroll, so it appears once a visitor is engaged. See [Display rules](/forms/display-rules). ## Exit-intent offer **A last word before they leave.** An exit-intent form appears the moment a visitor moves to close the tab or hit back — a final chance to capture an email or save a sale, without interrupting anyone who's still browsing. | | | | ----------------- | ----------------------------------------------- | | **Form type** | Exit-Intent | | **When it shows** | As the cursor moves to leave the page (desktop) | | **The offer** | "Wait — here's 10% off before you go" | * Best for visitors who didn't bite on the welcome popup. Because it only fires on *exit*, it never gets in the way of an active shopper. * Pair a slightly stronger or more urgent offer here — this is the goodbye, so make it count. * Exit-intent detection is a desktop behavior; on mobile, lean on the welcome popup and add-to-cart forms instead. ## Email capture on add-to-cart **Get the email before they reach checkout.** When a shopper adds an item but hasn't entered checkout yet, an add-to-cart form captures their email — so your [abandoned cart recovery](/automations/abandoned-cart-recovery) can reach them even if they never start checkout. | | | | ----------------- | ------------------------------------------------ | | **Form type** | Add-to-Cart | | **When it shows** | Right after a shopper adds an item to their cart | | **The offer** | "Save your cart — and get 10% off" | This is the form that quietly makes cart recovery work harder. Recovery email requires an email address; many shoppers add to cart but bail before the checkout step where they'd normally type it. Capturing it here closes that gap. ## Embedded newsletter signup **Always-on, never interrupting.** An embedded form sits inline in your page — a footer, a blog post, a landing page — so interested visitors can subscribe anytime without a popup ever appearing. | | | | ------------------ | --------------------------------------------------- | | **Form type** | Embed | | **Where it lives** | Inline in your content — footer, blog, landing page | | **The offer** | "Join our list for new arrivals and offers" | * Ideal for content pages and footers where a popup would feel heavy-handed. * Runs alongside your popups, not instead of them — a visitor who dismisses the popup can still subscribe from the footer. * Embedded forms don't use show-triggers; they're simply part of the page. ## "Notify me" back-in-stock waitlist **Capture demand for sold-out products.** On an out-of-stock product page, a form lets shoppers ask to be told when it returns — feeding the [back-in-stock automation](/automations/use-cases/back-in-stock). | | | | ----------------- | ---------------------------------------------- | | **Form type** | Welcome Popup or Embed, on out-of-stock pages | | **When it shows** | On product pages where the item is unavailable | | **The offer** | "Email me when this is back" | * Point this form at a product-specific [list](/audience/lists) so the restock alert reaches exactly the right people. * This turns a dead end ("sold out") into recovered revenue the moment you restock. ## Choosing between them | Your goal | Use | | -------------------------------------- | ------------------------------------------------------------------------ | | Grow the list at scale | **Welcome Popup** with a first-order discount | | Save visitors about to leave | **Exit-Intent** offer | | Make cart recovery reach more shoppers | **Add-to-Cart** email capture | | Subscribe without interrupting | **Embed** in footer / content | | Capture demand for sold-out items | "Notify me" form → [back in stock](/automations/use-cases/back-in-stock) | You can run several of these at once. A typical setup: a welcome popup for new visitors, an add-to-cart form to feed cart recovery, and an embedded form in the footer — each catching a different moment. Use [display rules](/forms/display-rules) to keep them from competing on the same page view. ## Next steps Build any of these, step by step. Control when, where, and to whom each form appears. The automation that converts new subscribers into buyers. # Welcome to Retainful Source: https://help.retainful.com/index Retainful is the customer retention platform for ecommerce. Recover abandoned carts, send email campaigns, automate customer journeys, and grow your audience. Retainful helps ecommerce stores turn one-time buyers into repeat customers. Connect your Shopify or WooCommerce store, and Retainful tracks your customers, carts, and orders so you can recover lost sales, send beautiful email campaigns, and automate your customer journeys — all from one dashboard. ## What can you do with Retainful? Automatically remind shoppers who left items behind and bring them back with a personalized email and a one-click recovery link. Design newsletters, sale announcements, and product launches with a drag-and-drop editor, then send them to exactly the right audience. Build welcome series, win-back flows, and post-purchase follow-ups with a visual builder — no code needed. Capture new subscribers with popups and signup forms, then organize them with lists and smart segments. Generate unique, single-use discount codes inside your emails to drive the next purchase. Go beyond the inbox with WhatsApp messages powered by the same automations. ## New to Retainful? Set up your account and send your first email in about 15 minutes. Install Retainful on your Shopify store in a few clicks. Link your WooCommerce store with the Retainful plugin. ## Set up for success Before you send your first campaign, take a few minutes to set up your sending foundation. It makes the difference between landing in the inbox and landing in spam. Link [Shopify](/integrations/shopify) or [WooCommerce](/integrations/woocommerce) so Retainful can sync your customers, orders, and products. Add a few DNS records to [send emails from your own domain](/email-setup/sending-domains). This dramatically improves deliverability. Bring your existing subscribers with you using the [import wizard](/audience/import-contacts). Start with [abandoned cart recovery](/automations/abandoned-cart-recovery) — it works around the clock from day one. ## For developers Building a custom integration or sending events from your own systems? Head to the [Developer documentation](/developers/overview) for the REST API reference, authentication, event types, and webhooks. REST API reference, authentication, rate limits, events, and custom integrations. Retainful helps ecommerce stores turn one-time buyers into repeat customers. Connect your Shopify or WooCommerce store, and Retainful tracks your customers, carts, and orders so you can recover lost sales, send beautiful email campaigns, and automate your customer journeys — all from one dashboard. ## What can you do with Retainful? Automatically remind shoppers who left items behind and bring them back with a personalized email and a one-click recovery link. Design newsletters, sale announcements, and product launches with a drag-and-drop editor, then send them to exactly the right audience. Build welcome series, win-back flows, and post-purchase follow-ups with a visual builder — no code needed. Capture new subscribers with popups and signup forms, then organize them with lists and smart segments. Generate unique, single-use discount codes inside your emails to drive the next purchase. Go beyond the inbox with WhatsApp messages powered by the same automations. ## New to Retainful? Set up your account and send your first email in about 15 minutes. Install Retainful on your Shopify store in a few clicks. Link your WooCommerce store with the Retainful plugin. ## Set up for success Before you send your first campaign, take a few minutes to set up your sending foundation. It makes the difference between landing in the inbox and landing in spam. Link [Shopify](/integrations/shopify) or [WooCommerce](/integrations/woocommerce) so Retainful can sync your customers, orders, and products. Add a few DNS records to [send emails from your own domain](/email-setup/sending-domains). This dramatically improves deliverability. Bring your existing subscribers with you using the [import wizard](/audience/import-contacts). Start with [abandoned cart recovery](/automations/abandoned-cart-recovery) — it works around the clock from day one. ## For developers Building a custom integration or sending events from your own systems? Head to the [Developer documentation](/developers/overview) for the REST API reference, authentication, event types, and webhooks. REST API reference, authentication, rate limits, events, and custom integrations. # Fix: "WooCommerce plugin not installed" Source: https://help.retainful.com/integrations/fix-woocommerce-plugin-not-installed What to do when Retainful reports the WooCommerce plugin is not installed, even though you've installed and activated it. You installed the Retainful plugin on your WordPress site, activated it, and Retainful still says the plugin isn't installed. This almost always means one of two things: a different plugin is installed, or your server is blocking Retainful's verification request. Work through the steps below in order. ## Step 1: Verify the correct plugin is installed Retainful looks for one specific plugin: **Email Marketing for WordPress and WooCommerce - Retainful**. An older or similarly-named plugin won't be detected. To check what you have: 1. Log in to your **WordPress admin**. 2. Go to **Plugins → Installed Plugins**. 3. Search for **Email Marketing for WordPress and WooCommerce - Retainful**. If it isn't there: 1. Go to **Plugins → Add New**. 2. Search for **Email Marketing for WordPress and WooCommerce - Retainful**. 3. Click **Install**, then **Activate**. Make sure you're on the latest version, and that the plugin is **Active** rather than merely installed. ## Step 2: Check for connection blocking If the plugin is installed and active and Retainful still can't see it, your site is almost certainly blocking our request. Common culprits: * Firewall rules * Security plugins such as Wordfence or Sucuri * Hosting-level protections, including Cloudflare or a server firewall * Any rule that restricts external API requests Retainful verifies the plugin by making a request **to** your store. If that request never arrives, the plugin looks missing from our side no matter how correctly it's installed. ## Step 3: Allow Retainful through Cloudflare If your site is behind Cloudflare, allow Retainful's IP address through your security rules. **On Cloudflare's Free plan**, Bot Fight Mode can't be scoped to a single IP — the custom rule below won't override it. You'll need to turn Bot Fight Mode off. On paid plans, follow the steps below instead. In the Cloudflare dashboard, select your domain, then go to **Security → Security rules**. 1 1 Click **Create rule** and choose **Custom rules**. Image Give the rule a name and match on Retainful's address: | Setting | Value | | --------- | --------------------- | | Rule name | `Allow Retainful` | | Field | **IP Source Address** | | Operator | **equals** | | Value | `52.15.242.48` | The expression preview should read `(ip.src eq 52.15.242.48)`. Image Under **Then take action**, choose **Skip**. Under **WAF components to skip**, tick all four: * All remaining custom rules * All rate limiting rules * All managed rules * All Super Bot Fight Mode rules Then click **Deploy**. WAF components to skip with all four options checked, and the Deploy button at the bottom of the form ## Step 4: Reconnect your store Return to Retainful and try connecting your store again. See [Connect WooCommerce](/integrations/woocommerce) for the full connection flow. ## Why this happens Installing the plugin correctly isn't enough on its own. Retainful confirms the installation by calling your store, so if your server blocks that call, verification fails and we report the plugin as missing — even though it's sitting there, installed and active. Allowing our IP through lets the check complete. ## Still stuck? If the connection still fails after allowing our IP: * Temporarily disable security plugins such as Wordfence or Sucuri and retry, to confirm whether they're the cause. * Check with your host about server-level firewalls that sit in front of WordPress. * Email [support@retainful.com](mailto:support@retainful.com), or [book an onboarding call](https://zcal.co/retainful/onboarding). # Connect Shopify Source: https://help.retainful.com/integrations/shopify Install Retainful on your Shopify store, enable storefront tracking, and verify everything is syncing. Connecting Shopify takes a few minutes. Once connected, Retainful automatically syncs your customers, products, and orders, tracks carts and checkouts, and can create discount codes in your store. ## What the connection enables * **Customer and order sync** — your Shopify customers appear as contacts, and every order is recorded for segmentation and revenue attribution. * **Cart and checkout tracking** — Retainful sees when a checkout starts and whether it completes, which powers abandoned cart recovery. * **Product sync** — your catalog is available inside the email editor for product blocks and recommendations. * **Discount codes** — automations can generate single-use Shopify discount codes on the fly. * **Storefront tracking pixel** — records product views and captures email consent at checkout. ## Install the app In Retainful, go to **Integrations → Shopify**, enter your store address (for example `mystore.myshopify.com`), and click **Connect**. You can also install directly from the Shopify App Store. Shopify shows the permissions Retainful needs — reading and writing customers, orders, checkouts, products, and discounts. Click **Install** to approve. Retainful begins importing your customers, products, and order history right away. Large stores may take a while; you can keep working in the meantime. The Integrations page shows **Connected** with your store name, and contacts start appearing under **Audience → Contacts**. ## Enable storefront tracking Two optional components run on your storefront and are worth enabling: ### Web pixel The web pixel records which products each visitor views and captures the email address and marketing consent shoppers enter at checkout. It powers browse-abandonment style targeting and improves cart recovery matching. Go to **Integrations → Shopify** and turn on the **Web Pixel**. Retainful registers it with Shopify automatically — no theme editing required. ### Theme app extension The theme app extension lets Retainful show [signup forms and popups](/forms/overview) on your storefront. 1. In Retainful, go to **Integrations → Shopify** and click **Enable** next to Theme App Extension. 2. If prompted, you'll be taken to your Shopify theme editor to flip the app embed on. If a popup isn't showing on your store, Retainful's popup requirements check will tell you exactly what's missing — usually the theme app embed being disabled or the app needing re-authorization. ## Re-sync your store If your data ever looks out of date, you can trigger a full re-import: go to **Integrations → Shopify** and click **Re-import**. This refreshes products, customers, and orders without affecting your automations. ## Disconnecting To disconnect, go to **Integrations → Shopify → Disconnect**, or uninstall the app from your Shopify admin. Either way, tracking stops immediately. Your existing contacts and history remain in Retainful. ## Billing through Shopify If you installed from the Shopify App Store, your Retainful subscription can be billed through Shopify and appears on your Shopify invoice. See [Billing and plans](/account/billing-and-plans). # Connect WooCommerce Source: https://help.retainful.com/integrations/woocommerce Install the Retainful plugin on your WordPress site and link your WooCommerce store. WooCommerce connects to Retainful through a WordPress plugin. The plugin watches your store for events — new orders, started checkouts, product views — and sends them to Retainful in real time. ## Requirements * WordPress with **WooCommerce active**. * The **Retainful plugin** (version 3.0 or later). * Your site must be reachable from the internet (for the connection handshake). ## Install and connect In your WordPress admin, go to **Plugins → Add New**, search for **Retainful**, install and activate it. In Retainful, go to **Integrations → WooCommerce**, enter your store URL (for example `https://mystore.com`), and click **Connect**. You'll be redirected to your WordPress admin to approve the connection. Once you approve, the plugin registers itself with Retainful and sets up event delivery automatically. Retainful imports your products, customers, and order history. Progress depends on store size. The Integrations page shows your store as **Connected**, and new orders start appearing in contact timelines within a minute or two of being placed. ## What the plugin tracks | Event | Used for | | ------------------------------- | ---------------------------------------------- | | Checkout started | Abandoned cart recovery | | Order placed / paid / fulfilled | Post-purchase automations, revenue attribution | | Order cancelled / refunded | Keeping reports accurate | | Product created / updated | Product blocks in emails | | Product viewed | Browse-based targeting | | Back in stock / out of stock | Back-in-stock automations | ## Staging sites and password-protected stores If your store sits behind HTTP basic authentication (common on staging sites), the connection check will fail. Go to **Integrations → WooCommerce → Advanced Auth** and enter the username and password so Retainful can reach your site. ## Troubleshooting Make sure the Retainful plugin is **activated** and WooCommerce is running. The connection check verifies both before starting. If your site uses Cloudflare with **Bot Fight Mode** enabled, it can block Retainful's connection request. Temporarily disable Bot Fight Mode (or add an exception) while connecting, then re-enable it. Check that your site's WordPress cron is running — some hosts disable it. Also confirm the store still shows as **Connected** in Retainful; if not, reconnect from the Integrations page. Coupons are created in your store by the plugin. Confirm the plugin is up to date and the store connection is healthy, then test the automation again. ## Disconnecting Go to **Integrations → WooCommerce → Disconnect** in Retainful, or deactivate the plugin in WordPress. Tracking stops immediately; your existing data stays in Retainful. # Quickstart Source: https://help.retainful.com/quickstart Go from signup to your first revenue-generating email in about 15 minutes. This guide takes you from a brand-new account to your first revenue-generating email. No technical knowledge required — if you can use your store's admin panel, you can do this. You need an active Shopify or WooCommerce store and access to your domain's DNS settings (usually wherever you bought your domain, like GoDaddy or Cloudflare). If someone else manages your DNS, you can invite them as a [team member](/account/team-and-roles) for step 3. ## Step 1: Create your account Go to [app.retainful.com/register](https://app.retainful.com/register) and create your account with your work email, or install the Retainful app directly from the Shopify App Store — Shopify creates your account automatically during installation. Click the verification link in your inbox. You can't send emails until your account email is verified. Give your organization a name (usually your store name) and pick your currency and timezone under **Settings → Currency & Timezone**. Reports and scheduled sends use these settings. ## Step 2: Connect your store Retainful needs to see your customers, carts, and orders to do its job. 1. Go to **Integrations** in the sidebar and choose **Shopify**. 2. Enter your store address (for example `mystore.myshopify.com`) and click **Connect**. 3. Approve the permissions on the Shopify screen that opens. Retainful immediately starts syncing your products, customers, and orders. See [Connect Shopify](/integrations/shopify) for details, including enabling the storefront tracking pixel. 1. Install the **Retainful** plugin on your WordPress site. 2. In Retainful, go to **Integrations → WooCommerce** and enter your store URL. 3. Approve the connection request that appears in your WordPress admin. See [Connect WooCommerce](/integrations/woocommerce) for plugin requirements and troubleshooting. When the connection succeeds, you'll see your store listed as **Connected** on the Integrations page, and contacts begin appearing under **Audience → Contacts**. ## Step 3: Set up your sending domain Out of the box, Retainful can send from a shared domain, but emails sent from **your own domain** land in the inbox far more reliably and show your brand in the "from" address. Go to **Settings → Email → Domains** and add the domain you want to send from (for example `mystore.com`). Retainful shows you a short list of DNS records (DKIM, SPF, DMARC, and return-path). Add each one at your DNS provider — copy and paste them exactly. Back in Retainful, click **Verify** on each record. DNS changes can take up to a few hours to propagate, so don't worry if it doesn't verify instantly. Under **Settings → Email → From Addresses**, add the address you'll send from (like `hello@mystore.com`) and verify it. See [Sending domains](/email-setup/sending-domains) for a record-by-record walkthrough. ## Step 4: Bring in your audience If you're moving from another email tool, export your subscribers there as a CSV file, then: 1. Go to **Audience → Imports** and start the import wizard. 2. Upload your CSV file. 3. Match your file's columns to Retainful's contact fields (email, first name, and so on). 4. Choose which list the contacts should join and confirm their subscription status. Only import people who gave you permission to email them. Importing purchased or scraped lists hurts your deliverability and may violate anti-spam laws. More detail in [Import contacts](/audience/import-contacts). ## Step 5: Turn on abandoned cart recovery This is the fastest way to see revenue from Retainful — it recovers sales you're currently losing. 1. Go to **Automations** and click **Templates**. 2. Pick the **Abandoned Cart Recovery** template. 3. Review the pre-built emails — adjust the wording, add your logo, or attach a discount. 4. Click **Publish**. From now on, when a shopper leaves the checkout without paying, Retainful automatically follows up with your recovery emails. See the full guide: [Abandoned cart recovery](/automations/abandoned-cart-recovery). ## Step 6: Send your first campaign 1. Go to **Campaigns** and click **Create campaign**. 2. Pick a template you like (you can change everything about it). 3. Design your email in the drag-and-drop editor. 4. Choose your audience — start with the list you imported. 5. Send yourself a **test email**, then schedule or send. See [Create a campaign](/campaigns/create-a-campaign) for the full walkthrough. ## What's next? Target customers by behavior — like "bought in the last 30 days" or "opened but didn't click." Grow your list with a popup that offers a welcome discount. Welcome series, win-back campaigns, post-purchase thank-yous, and more. Learn what opens, clicks, and conversions tell you about your emails. This guide takes you from a brand-new account to your first revenue-generating email. No technical knowledge required — if you can use your store's admin panel, you can do this. You need an active Shopify or WooCommerce store and access to your domain's DNS settings (usually wherever you bought your domain, like GoDaddy or Cloudflare). If someone else manages your DNS, you can invite them as a [team member](/account/team-and-roles) for step 3. ## Step 1: Create your account Go to [app.retainful.com/register](https://app.retainful.com/register) and create your account with your work email, or install the Retainful app directly from the Shopify App Store — Shopify creates your account automatically during installation. Click the verification link in your inbox. You can't send emails until your account email is verified. Give your organization a name (usually your store name) and pick your currency and timezone under **Settings → Currency & Timezone**. Reports and scheduled sends use these settings. ## Step 2: Connect your store Retainful needs to see your customers, carts, and orders to do its job. 1. Go to **Integrations** in the sidebar and choose **Shopify**. 2. Enter your store address (for example `mystore.myshopify.com`) and click **Connect**. 3. Approve the permissions on the Shopify screen that opens. Retainful immediately starts syncing your products, customers, and orders. See [Connect Shopify](/integrations/shopify) for details, including enabling the storefront tracking pixel. 1. Install the **Retainful** plugin on your WordPress site. 2. In Retainful, go to **Integrations → WooCommerce** and enter your store URL. 3. Approve the connection request that appears in your WordPress admin. See [Connect WooCommerce](/integrations/woocommerce) for plugin requirements and troubleshooting. When the connection succeeds, you'll see your store listed as **Connected** on the Integrations page, and contacts begin appearing under **Audience → Contacts**. ## Step 3: Set up your sending domain Out of the box, Retainful can send from a shared domain, but emails sent from **your own domain** land in the inbox far more reliably and show your brand in the "from" address. Go to **Settings → Email → Domains** and add the domain you want to send from (for example `mystore.com`). Retainful shows you a short list of DNS records (DKIM, SPF, DMARC, and return-path). Add each one at your DNS provider — copy and paste them exactly. Back in Retainful, click **Verify** on each record. DNS changes can take up to a few hours to propagate, so don't worry if it doesn't verify instantly. Under **Settings → Email → From Addresses**, add the address you'll send from (like `hello@mystore.com`) and verify it. See [Sending domains](/email-setup/sending-domains) for a record-by-record walkthrough. ## Step 4: Bring in your audience If you're moving from another email tool, export your subscribers there as a CSV file, then: 1. Go to **Audience → Imports** and start the import wizard. 2. Upload your CSV file. 3. Match your file's columns to Retainful's contact fields (email, first name, and so on). 4. Choose which list the contacts should join and confirm their subscription status. Only import people who gave you permission to email them. Importing purchased or scraped lists hurts your deliverability and may violate anti-spam laws. More detail in [Import contacts](/audience/import-contacts). ## Step 5: Turn on abandoned cart recovery This is the fastest way to see revenue from Retainful — it recovers sales you're currently losing. 1. Go to **Automations** and click **Templates**. 2. Pick the **Abandoned Cart Recovery** template. 3. Review the pre-built emails — adjust the wording, add your logo, or attach a discount. 4. Click **Publish**. From now on, when a shopper leaves the checkout without paying, Retainful automatically follows up with your recovery emails. See the full guide: [Abandoned cart recovery](/automations/abandoned-cart-recovery). ## Step 6: Send your first campaign 1. Go to **Campaigns** and click **Create campaign**. 2. Pick a template you like (you can change everything about it). 3. Design your email in the drag-and-drop editor. 4. Choose your audience — start with the list you imported. 5. Send yourself a **test email**, then schedule or send. See [Create a campaign](/campaigns/create-a-campaign) for the full walkthrough. ## What's next? Target customers by behavior — like "bought in the last 30 days" or "opened but didn't click." Grow your list with a popup that offers a welcome discount. Welcome series, win-back campaigns, post-purchase thank-yous, and more. Learn what opens, clicks, and conversions tell you about your emails. # Message templates Source: https://help.retainful.com/whatsapp/message-templates Create, submit, and manage the Meta-approved templates your WhatsApp messages are built from. Every business-initiated WhatsApp message starts from a **template** that Meta has reviewed and approved. Templates contain fixed text plus variables you fill at send time — like the contact's name or their checkout link. ## Template categories Meta sorts templates into three categories, which affect pricing and review: | Category | For | Example | | ------------------ | ---------------------------------------------------------- | ------------------------- | | **Marketing** | Promotions and offers. | | | **Utility** | Transactional updates tied to an action the customer took. | | | **Authentication** | One-time passcodes. | Rarely needed for stores. | ## Create and submit a template Go to **Settings → WhatsApp → Message Templates** and click **Create template**. Write the body, marking variables where personal content goes — for example the contact's first name, a coupon code, or your shop URL. Retainful submits the template to Meta. Status shows as **Pending** while Meta reviews — typically minutes to a day. **Approved** templates appear in the Send WhatsApp Message step in your automations. **Rejected** templates show Meta's reason; edit and resubmit. ## Getting approved on the first try * Match the category to the content — marketing copy in a "utility" template is the most common rejection. * Write complete sentences around variables; a template that's mostly placeholders gets rejected. * Include your business name so recipients know who's writing. * Avoid prohibited content (per Meta's commerce policy) and excessive caps or punctuation. ## Templates in cart recovery The highest-value pattern: a recovery template with the **checkout link** (optionally with a coupon applied) as a variable. Combined with a [Coupon step](/automations/coupons), each customer gets a personal code and a one-tap path back to checkout. # Set up WhatsApp Source: https://help.retainful.com/whatsapp/setup Connect your WhatsApp Business account to send messages from your automations. WhatsApp gives you a second channel with open rates email can only dream about. Once connected, the **Send WhatsApp Message** step becomes available in your [automations](/automations/steps). ## What you need * A **Meta Business account** (the same kind used for Facebook/Instagram business tools). * A **phone number** to dedicate to WhatsApp Business — it can't be a number already active on the regular WhatsApp app. * Admin access to approve the connection. ## Connect your account Go to **Settings → WhatsApp** and click **Connect**. You'll be taken through Meta's embedded signup. Log in to the Meta account that owns (or will own) your WhatsApp Business profile, and create or select your WhatsApp Business account. Add the number you'll send from and verify it with the code Meta sends. Manage numbers later under **Settings → WhatsApp → Phone numbers**. When the flow completes, the WhatsApp settings page shows your account as **Connected**. ## How sending works WhatsApp is stricter than email: * Business-initiated messages must use a **pre-approved template** — see [Message templates](/whatsapp/message-templates). * Contacts need a **phone number with opt-in** — collect consent through your signup forms or checkout. * Recipients can opt out at any time; configure your unsubscribe behavior under **Settings → WhatsApp → Unsubscribe**. Only message contacts who explicitly agreed to receive WhatsApp messages from you. Meta enforces quality ratings per number — too many blocks or reports and your number's sending limits drop. ## Where WhatsApp shines * **Cart recovery** — a WhatsApp nudge with the checkout link, often alongside the email sequence. * **Order updates** — utility messages like shipping confirmations. * **Time-sensitive offers** — a coupon expiring tonight gets seen on WhatsApp in minutes.