Usage
How a workflow works
A workflow has three parts, and the editor asks for them in this order:
- When: one trigger. An order was placed, an order moved into "shipped", somebody registered, a cart went quiet.
- Only if: conditions. Rules in a group must all be true; any one group being true is enough. No conditions at all means the workflow runs every time its trigger fires.
- Then: an ordered list of steps. Each step has a delay that is waited before it runs, so the list reads top to bottom as a timeline.
When an order is placed
Only if it is the customer's first order
Then immediately tag them "first-time-buyer"
after 2 days email "Welcome, a few things worth knowing"
after 12 days email "How did we do?"
Each triggered instance of a workflow is a run. You can watch runs under Lyfe → Activity, open one to see exactly what each step did, and cancel or advance it by hand.
Delays
A delay can be in minutes, hours, days or weeks. The first step's delay is counted from the moment the trigger fired; every later step's delay is counted from when the step before it ran.
A step with no delay (shown as "immediately") runs straight away. If the first step has no delay, it runs during the request that triggered the workflow, which for an order trigger is the customer's checkout. A failure there is caught and logged and cannot stop the checkout, but an immediate email does add the time it takes to send. To keep that work off the customer's request entirely, give the first step a delay of a minute or more. Every step after a delay is run by the scheduler (see Installation).
The Safety section
| Option | What it does |
|---|---|
| Enabled | Off means the trigger never starts this workflow. Leave it off until you have tested the workflow in the simulator. |
| Dry run | Runs normally but sends nothing. Every step is recorded, and emails and texts appear in the message log as "simulated". |
| Re-check conditions before every step | On by default. Each time a run resumes after a delay or a retry, the conditions are evaluated again; if they no longer match, the rest of the run is dropped and recorded as skipped. Leave this on for anything with a delay: the customer may have unsubscribed, refunded or bought again. |
| Store | Restrict the workflow to one Commerce store. All stores is the default. |
| Run at most this many times per contact (Pro) | Once a contact has this many runs of the workflow, of any outcome, it will not start for them again. |
| Cooldown per contact (Pro) | Minutes. A contact who started this workflow inside the cooldown will not start it again. |
| Send window (Pro) | From and To in 24-hour HH:MM, plus Only on these days. |
The send window is in your site's timezone. When a step comes due outside the window, the run
waits until the window next opens instead of sending at four in the morning. Overnight windows
such as 22:00 to 06:00 work. If the days you tick mean the window never opens in the coming
week, Lyfe sends anyway and logs a warning, rather than holding the campaign forever.
What stops a workflow running twice
Every run carries a deduplication key, and the database refuses a second run of the same workflow with the same key. A retried webhook, a Commerce event that fires twice, or an overlapping sweep collides there and is dropped silently, because that is the point.
| Trigger | Runs at most once per workflow for each |
|---|---|
| Order placed | order |
| Order paid in full | order |
| Order status changed | time the order enters a status (an order that is re-shipped is a new event) |
| Order refunded | refund transaction (each partial refund is separate) |
| Specific product purchased | order |
| Account created | user |
| Cart abandoned | abandonment (a customer who comes back and abandons again is a new event) |
| Tag added to a contact | time the tag is added |
| Scheduled contact scan | contact, per period (see the trigger) |
| Manual only | never deduplicated; use a run limit or cooldown |
Workflows that trigger each other (one changes an order status, which triggers another that changes it back) are stopped after three levels and the refusal is logged.
Retries and stale steps
A step that fails, for example because the mail server refused the message, is retried after Wait before retrying minutes, up to Retry a failed step attempts in total. After that the run is marked failed and its error is shown on the run.
A step that came due more than Abandon steps overdue by more than hours ago is abandoned, not sent late. See Configuration.
Triggers
| Trigger | Handle | Fires when | Settings |
|---|---|---|---|
| Order placed | order.placed | Commerce completes an order. | None |
| Order paid in full | order.paid | An order's balance reaches zero. For offline payments this can be days after it was placed. | None |
| Order status changed | order.status.changed | An order moves into a status. | Moved into (any status if none ticked), Moved out of (optional) |
| Order refunded | order.refunded | A refund transaction succeeds. | None |
| Specific product purchased | order.product.purchased | A completed order contains one of the chosen products. | Products, or Or SKUs (one per line, case-insensitive). With neither set, it never fires. |
| Account created | user.created | A new Craft user is saved, whether they registered on the front end or were created in the control panel. | None |
| Manual only | manual | Never on its own. Started by hand; see Starting a workflow by hand. | None |
| Cart abandoned (Pro) | cart.abandoned | A tracked cart with items in it has been inactive for longer than the abandonment window. | Minimum cart value, Only carts with an email address (on by default) |
| Tag added to a contact (Pro) | contact.tagged | A tag the contact did not already have is added, by a workflow or in the control panel. | Tags (one per line, case-insensitive; empty means any tag) |
| Scheduled contact scan (Pro) | scheduled.contacts | On a timer, for each contact that passes the prefilter. | See below |
Scheduled contact scan
Nothing happens when a customer stops buying, so there is no event to listen for. A scan walks your contacts instead. It is what win-backs and VIP tagging are built on.
| Setting | What it does |
|---|---|
| How often to scan | Daily, weekly or monthly. A scan runs when that long has passed since the last one. |
| At least this many days since the last order | Prefilter. Contacts who have never ordered are excluded. |
| At most this many days since the last order | Prefilter. |
| At least this many orders | Prefilter. |
| Repeat for the same contact every | Days. Empty means at most once per contact, ever. 0 means once per scan period. A number means once per window of that many days. |
The prefilter fields are a fast database filter over the figures Lyfe keeps on each contact. They are optional: your conditions are still evaluated against live Commerce data for every candidate, so a stale figure can only cost a wasted check, never a wrong send. Every contact that passes the prefilter is checked, in batches of 500, so on a large store a focused prefilter keeps the scan quick.
The figures are kept up to date as orders complete. After a bulk import, or if you suspect they
have drifted, run php craft lyfe/contacts/sync-stats.
A store restriction on the workflow does not narrow a scan: contacts are not tied to a store.
Conditions
Conditions are grouped in the editor by what they look at. Text comparisons ignore case and surrounding spaces.
Order
| Condition | Handle | Compares |
|---|---|---|
| Order total | order.total | The order's total price |
| Order subtotal (items only) | order.itemTotal | Line items before shipping, tax and order-level adjustments |
| Number of items | order.itemCount | Total quantity across line items |
| Products in the order | order.products | Products on the order's line items |
| SKUs in the order | order.skus | SKUs on the order's line items |
| Coupon code used | order.coupon | The coupon code |
| Payment gateway | order.gateway | The gateway's handle |
| Shipping country | order.shippingCountry | The shipping address's country |
| Order status | order.status | The order's current status |
| Order currency | order.currency | The order's currency code |
| Is the customer's first order | order.isFirst | Whether this contact completed any order before this one. Asked relative to the order, so a step nine days later gets the answer that was true at checkout. |
| Order is paid in full | order.isPaid | Whether the order's balance is zero |
Customer
| Condition | Handle | Compares |
|---|---|---|
| Email address | contact.email | The contact's email |
| Phone number | contact.phone | The contact's phone number. "is not empty" is the useful check before an SMS. |
| Has a user account | contact.hasAccount | Whether the contact has a Craft user account |
| Lifetime order count | contact.orderCount | Completed orders, read live from Commerce |
| Lifetime spend | contact.totalSpend | Total of completed orders, read live from Commerce |
| Days since first order | contact.daysSinceFirstOrder | Whole days since the first completed order |
| Days since last order | contact.daysSinceLastOrder | Whole days since the most recent completed order. A contact who has never ordered has no value, so "at least 120" does not match them. |
| Tags | contact.tags | The contact's tags |
| User groups | contact.userGroups | The user account's groups |
| Subscribed to marketing email | contact.subscribedEmail | The contact's email consent |
| Subscribed to SMS | contact.subscribedSms | The contact's SMS consent |
Lifetime figures match orders by the contact's email address and by their user account, so a customer who ordered both as a guest and logged in is counted once, as one person. Spend is a plain sum of order totals across every store and currency.
Timing
| Condition | Handle | Compares |
|---|---|---|
| Trigger date | trigger.date | When the trigger fired |
| Day of the week | trigger.dayOfWeek | The day the trigger fired, in the site's timezone |
| Hour of the day (0-23) | trigger.hourOfDay | The hour the trigger fired, in the site's timezone |
Workflow
| Condition | Handle | Compares |
|---|---|---|
| Times this workflow has run | workflow.runCount | All runs of this workflow. A blunt cap on a campaign. |
| Times this contact has been through this workflow | workflow.contactRunCount | This contact's runs of this workflow. Lets you act on the count ("on the third reminder, add a discount") rather than only refuse. |
Operators
Which operators a condition offers depends on what it compares:
| Value | Operators |
|---|---|
| Numbers, amounts, days | is, is not, is greater than, is at least, is less than, is at most |
| A choice from a list (status, gateway, country, day) | is, is not, is one of, is none of |
| A set (products, SKUs, tags, user groups) | contains (any of), does not contain, is empty, is not empty |
| Yes or no | is |
| A date | is greater than, is at least, is less than, is at most |
| Text | is, is not, contains, does not contain, starts with, ends with, is one of, is none of, is empty, is not empty |
A numeric condition that has nothing to compare fails rather than counting as zero. "Order total is less than 10" does not match a workflow started for a contact with no order.
Actions
| Action | Handle | What it does |
|---|---|---|
| Send an email | email.send | Renders the subject and body as Twig and sends through Craft's mailer. |
| Add tags to the contact | contact.tag.add | Adds tags (one per line; Twig allowed). On Pro, a new tag fires Tag added to a contact. |
| Remove tags from the contact | contact.tag.remove | Removes tags. |
| Add a note to the order | order.note | Writes a line into the order's history without changing its status, so staff can see what the automation did. |
| Send a webhook | webhook.post | Sends a POST, PUT or GET to a URL. Admin-only; see Who can write what. |
| Write a note in the run log | log.note | Records a line on the run and does nothing else. Useful while you are working a workflow out. |
| Stop the run if... | run.stopIf | Ends the run when its own conditions match, or when they no longer match. |
| Send an SMS (Pro) | sms.send | Texts the contact through the configured transport. |
| Change the order status (Pro) | order.status.set | Moves the order into another status, with an optional history message. Commerce's own status emails fire as usual. |
| Add to a user group (Pro) | user.group.add | Adds the contact's user account to groups, keeping its existing groups. Skipped for guests. |
| Remove from a user group (Pro) | user.group.remove | Removes the account from groups. |
| Generate a discount code (Pro) | commerce.discount.code | Adds a unique coupon code to one of your Commerce discounts. |
| Unsubscribe the contact (Pro) | contact.unsubscribe | Turns off marketing email, SMS, or both. |
| Cancel this contact's pending runs (Pro) | run.cancelOthers | Cancels the contact's waiting runs of the chosen workflows, or of every workflow on the chosen triggers. With nothing chosen, it cancels every other workflow's runs. A choice that matches no workflow cancels nothing. |
Send an email
| Setting | Notes |
|---|---|
| Subject, Body | Twig. The body is sent as HTML, with a plain-text version generated from it, and is wrapped in your email layout template if you set one. |
| To | Leave empty to send to the contact. |
| CC, BCC | Separate several addresses with commas, semicolons or new lines. |
| Reply to | Overrides the default reply-to address. |
| Transactional | Sends even to contacts who have unsubscribed from marketing email. Only correct for receipts and shipping notices. |
Unless Transactional is on, an email to an unsubscribed contact is not sent. The refusal is recorded in the message log as "suppressed", and the run stops there, so later steps do not run. This applies even when To is set to a different address, because the run is still about that contact.
The Post-purchase thank you and Alert the team to a large order presets ship with Transactional on. Review that before you turn them on.
Always include {{ unsubscribeUrl }} in marketing email.
Send an SMS
The body is Twig. Keep it under 160 characters after rendering if you want it to go as a single segment. To overrides the recipient; leave it empty to text the contact's own number.
There is no transactional override for SMS. A contact who has not opted in to SMS is never texted; the attempt is recorded as "suppressed" and the run stops.
Stop the run if...
Stop when is either the conditions match or the conditions no longer match, and the conditions use the same builder as the workflow's own. The usual use is in an abandoned-cart sequence: before the second email, stop if the order has been paid.
Generate a discount code
Lyfe does not create discounts. Set up the discount in Commerce, with the rules you want, and choose it here. The step adds a new coupon code to it, so the discount's own rules decide what the code is worth.
| Setting | Notes |
|---|---|
| Discount | Discounts from every store are listed. |
| Code prefix | Default LYFE. Twig allowed. Letters, numbers and hyphens are kept and uppercased. |
| Random characters | Default 8, between 4 and 24. Codes are checked for uniqueness against existing coupons. |
| Maximum uses | Default 1. 0 means unlimited. |
| Offer expiry for the email (days) | Default 30. 0 for none. Sets {{ data.discountExpiry }}; see below. |
The code is available to later steps as {{ data.discountCode }}, and the expiry date as
{{ data.discountExpiry }}.
Two things to know:
- The expiry is for your copy, not enforced by Commerce. Commerce coupons have no expiry date of their own, so the code stays valid until it reaches its maximum uses or the discount itself ends. If a code must stop working on a date, set an end date on the discount.
- The code is kept with the run, so the email that uses it can come any time later in the workflow, after a delay or a retry.
Send a webhook
| Setting | Notes |
|---|---|
| URL | Twig allowed. |
| Method | POST, PUT or GET. GET sends no body. |
| Body | Twig. Leave empty to send Lyfe's own JSON summary: the workflow handle, when it triggered, the contact (id, email, phone, names, tags), the order (id, number, reference, total, currency, email, status) and data. |
| Content type | Default application/json. |
| Extra headers | One Header: value per line. A value that is exactly $VARIABLE is replaced with that environment variable. Put any prefix such as Bearer inside the variable. |
| Timeout (seconds) | Default 10. |
Any 2xx response counts as success. Anything else fails the step, records the status and the first 300 characters of the response, and is retried like any other failure.
Variables in emails and SMS
Every text setting that says "Twig" is rendered against the run. Subjects work too:
Your order {{ order.reference }}.
| Variable | What it is |
|---|---|
contact | The contact: email, firstName, lastName, phone, and their tags |
order | The order, for order and cart triggers. Empty for account, tag, scan and manual triggers. |
user | The Craft user account, if there is one |
workflow | The workflow: name, handle |
stats | Live purchase history: orderCount, totalSpend, firstOrderDate, lastOrderDate |
unsubscribeUrl | The contact's personal unsubscribe link |
cartRestoreUrl | For an abandoned cart (Pro), a link that puts the cart back in the customer's session. Empty otherwise, and empty once the cart has been turned into an order. |
data | Values specific to the trigger or earlier steps (below) |
data contains:
| Key | Set by |
|---|---|
data.fromStatus, data.toStatus | Order status changed (status handles) |
data.refundAmount | Order refunded |
data.itemTotal, data.lineItemCount | Cart abandoned |
data.tag | Tag added to a contact |
data.discountCode, data.discountExpiry | Generate a discount code |
<p>Hi {{ contact.firstName ?? 'there' }},</p>
<p>Your order {{ order.reference }} is on its way.</p>
{% if stats.orderCount > 1 %}<p>Thanks for coming back.</p>{% endif %}
<p><a href="{{ unsubscribeUrl }}">Unsubscribe</a></p>
In an untrusted step
A step that a non-admin wrote and no admin has approved renders in Craft's Twig sandbox, against plain values rather than objects (see Who can write what). The same variable names work, with these values:
contact:id,email,firstName,lastName,fullName,phone,tagsorder:id,number,shortNumber,reference,email,currency,isCompleted,dateOrdered,itemSubtotal,itemSubtotalAsCurrency,totalPrice,totalPriceAsCurrency,totalQty, andlineItems, each withdescription,sku,qty,price,priceAsCurrency,total,totalAsCurrencyuser:id,email,firstName,lastName,fullNameworkflow:name,handlestats,data,unsubscribeUrlandcartRestoreUrl, as above
Use the ...AsCurrency values for prices, because the commerceCurrency filter is not allowed in
the sandbox.
{% for item in order.lineItems %}
{{ item.qty }} x {{ item.description }}: {{ item.totalAsCurrency }}
{% endfor %}
Total: {{ order.totalPriceAsCurrency }}
On your own templates
craft.lyfe is available in front-end templates:
| Method | Returns |
|---|---|
craft.lyfe.contact(email) | The contact for an address, or for the logged-in user with no argument |
craft.lyfe.unsubscribeUrl(contactOrEmail) | That contact's unsubscribe link |
craft.lyfe.cartRestoreUrl(order, redirect) | The restore link for a tracked cart, optionally redirecting afterwards |
craft.lyfe.cart(order) | Lyfe's tracking record for a cart |
craft.lyfe.tags(contact) | A contact's tags |
craft.lyfe.isPro() | Whether the Pro edition is active |
The simulator
Lyfe → Simulator answers "would this run, and if not, why not?" against a real order or contact. Enter an order ID, or a contact's email, optionally limit it to one workflow, and click Simulate. For each workflow you get:
- anything blocking it: Lyfe switched off, the workflow disabled, Pro required on Lite, the trigger's own settings ruling the subject out, or the contact being over the run limit
- every condition, with the actual value and whether it matched
- every step, with when it would land and what it would do: who it would email and with what subject, which tags it would add, which runs it would cancel
✗ Win back a lapsed customer
· The conditions do not match.
✓ Subscribed to marketing email (yes) is yes
✗ Days since last order (12) is at least 120
The simulator uses the same code as a live run, in simulation mode, so what it shows is what the engine would do. It sends nothing, but simulated emails and texts are written to the message log marked "simulated" so you can read the rendered copy. For triggers that carry extra data, the simulator fills in a plausible stand-in, for example the first status a status trigger listens for.
Links such as Test against an order in the workflow editor and Simulate against this order on Commerce's order screen open the simulator with the form filled in. Nothing runs until you click Simulate.
From the console:
php craft lyfe/workflows/simulate 1234 # enabled and disabled workflows
Dry run
A dry run is a live run that sends nothing. Turn it on per workflow under Safety, or for everything with Global dry run in the settings. Real triggers start real runs, delays are waited, conditions are re-checked, and every step records what it would have done. Emails and texts are logged as "simulated" with their rendered content. Actions that change data (tags, order notes, status changes, discount codes, cancellations) are not performed either; the run records what they would have done.
Dry run is the way to watch a week of real traffic before going live.
Activity, messages and the log
| Screen | Shows |
|---|---|
| Activity | Every run, filterable by status and workflow. Open a run to see each step's outcome and the messages it produced. Run now advances a waiting run immediately (and resets its retry count); Cancel stops it; Process due steps now does what the cron job does. |
| Messages | Every email and SMS: sent, failed, suppressed (by consent) or simulated, with the rendered content. |
| Log | Engine events outside any single run: workflows skipped because they need Pro, failures caught during a customer's request, scans, consent changes made on the unsubscribe page. Filter by level and category (engine, runs, carts, scans, email, consent). |
Commerce's order edit screen also gets a Lyfe panel showing the runs for that order and the contact's recent messages.
Contacts, consent and tags
Lyfe keeps its own contact list, because most Commerce customers never register an account and consent has to be stored somewhere. A contact is identified by email address, without regard to case, so a guest order, a registered account and a manual entry for the same address are one contact.
Lyfe creates a contact when an order or a new user account starts one of your workflows, and on Pro for every tracked cart with an email address (see Create contacts automatically in Configuration). An order that no enabled workflow listens for does not create one, so run the backfill below to cover your existing customers. You can also add and edit contacts under Lyfe → Contacts.
Each contact has:
- Email, First name, Last name and Phone. Names come from the order's billing
address, or the user account. A phone number is taken from the address only if your address
field layout has a field with the handle
phone. Lyfe fills in blanks as it learns more but never overwrites a value that is already set. - Tags, used for segments. Edit them as a comma-separated list on the contact screen.
- Subscribed to marketing email and Subscribed to SMS.
- Purchase history, read live from Commerce, with Refresh the cached figures to update the copy that scheduled scans filter on.
- Recent runs and messages, and the contact's unsubscribe link.
To build contacts from orders placed before you installed Lyfe:
php craft lyfe/contacts/backfill
It is safe to run again; existing contacts are updated, never duplicated.
Consent
Email and SMS consent are separate. New contacts get the defaults from the settings: subscribed to email, not subscribed to SMS.
A contact can change their own consent through {{ unsubscribeUrl }}, which opens a page where
they choose which channels to keep. You can change it on the contact screen, with the
Unsubscribe the contact action (Pro), or from the console:
php craft lyfe/contacts/unsubscribe someone@example.com # email and SMS
Lyfe never sends marketing email to a contact who has unsubscribed from it, and never texts a contact who has not opted in to SMS. Each refusal is recorded in the message log as "suppressed". Steps that do not send anything, such as tagging, still run up to that point.
Tags and chaining (Pro)
Adding a tag fires Tag added to a contact, so one workflow can hand off to the next. A weekly
scan tags big spenders vip; a separate workflow triggered by the vip tag sends a thank-you and
adds them to a user group. Neither workflow needs to know about the other.
Starting a workflow by hand
On a contact's screen, Run a workflow for this contact starts any workflow for them. From the
console, lyfe/workflows/run-for-tag starts one for every contact with a tag, after asking you to
confirm:
php craft lyfe/workflows/run-for-tag spring-campaign wholesale
A manual start ignores whether the workflow is enabled and what its trigger is set to listen for. It does not ignore its conditions, its run limit or its cooldown: those protect the customer. A manual run has no order attached, so conditions about the order fail. Use the Manual only trigger for workflows that only ever start this way.
Carts (Pro)
With Track carts on, Lyfe records when each cart with items in it was last touched. When a cart
has been inactive for Consider a cart abandoned after minutes, the next sweep marks it
abandoned and fires Cart abandoned. Sweeps run with lyfe/run/due, or from Check for
abandoned carts now on the Carts screen.
- A cart emptied by the customer stops being tracked.
- A customer who comes back to an abandoned cart puts it back in play. If they abandon it again, that is a new abandonment and starts a new run.
- When an abandoned cart becomes an order it is marked recovered; a cart that became an order without being abandoned is marked placed.
- Carts inactive for longer than Stop tracking a cart after days are dropped.
The Carts screen shows each cart's status, value and last activity, and totals for abandoned and recovered value.
{{ cartRestoreUrl }} links to Commerce's own load-cart action, which puts the cart back in the
customer's browser session.
A complete abandoned-cart setup is two presets: Abandoned cart recovery sends two emails and stops if the order is paid in between, and Stop chasing recovered carts cancels any pending chase when the customer places an order.
The preset library
Every preset installs disabled, with copy marked EDIT ME. Installing one twice gives two
separate workflows.
| Preset | Handle | Edition | What it does |
|---|---|---|---|
| Post-purchase thank you | post-purchase-thanks | Lite | Email an hour after checkout. Transactional. |
| Review request | review-request | Lite | Email 14 days after the order moves into completed. |
| Replenishment reminder | replenishment | Lite | Email 45 days after a chosen product is bought. Choose the products before enabling. |
| New account welcome | welcome-account | Lite | Welcome email, then a nudge three days later if they have not ordered. |
| First-time buyer follow-up | first-order-vs-repeat | Lite | Tags first-time-buyer, emails two days later. First orders only. |
| Repeat customer follow-up | repeat-customer | Lite | Email two days after the second and later orders. |
| Alert the team to a large order | high-value-alert | Lite | Emails your team when an order total is 1000 or more. Set the address. |
| Follow up on a refund | refund-follow-up | Lite | Tags refunded, emails a day later. |
| Abandoned cart recovery | abandoned-cart | Pro | Two emails, an hour and a day after abandonment. |
| Stop chasing recovered carts | cart-recovered-cleanup | Pro | Cancels pending abandoned-cart runs when an order is placed. |
| Win back a lapsed customer | win-back | Pro | Daily scan for 120 days without an order; tags lapsed and emails. |
| Clear the lapsed tag when they return | win-back-return | Pro | Swaps lapsed for won-back on their next order. |
| Tag VIP customers | vip-tagging | Pro | Weekly scan; tags vip at 500 or more lifetime spend. Set the threshold in your currency. |
| Text the customer when an order ships | shipped-sms | Pro | SMS when the order moves into shipped, to opted-in contacts with a phone number. |
The status presets assume your Commerce order statuses have the handles completed and
shipped. If yours are different, change the trigger before enabling.
Stop chasing recovered carts cancels the contact's pending runs of every workflow triggered by an abandoned cart, and nothing else, so the thank-you and review emails the same order starts are left alone.
php craft lyfe/workflows/library
php craft lyfe/workflows/install win-back
Console commands
| Command | What it does |
|---|---|
lyfe/run/due | The cron entry point. Advances due runs, then on Pro sweeps carts and runs due scans. Options: --limit=N, --queue (push a queue job instead), --runs-only (skip the cart sweep and scans). Exits non-zero if any run failed. |
lyfe/run/status | Edition, whether Lyfe and global dry run are on, runs by status, and how many are due now. |
lyfe/run/cancel <runId> | Cancel one run. |
lyfe/run/prune [days] | Delete finished runs, messages and log entries older than days, or Keep history for if omitted. |
lyfe/workflows | List workflows with trigger, step count, status and run counts. |
lyfe/workflows/library | List the presets. |
lyfe/workflows/install <preset> | Install a preset, disabled. |
lyfe/workflows/simulate <orderId> | Simulate every workflow against an order. Add --all to include disabled workflows. |
lyfe/workflows/run-for-tag <workflow> <tag> | Start a workflow (by handle) for every contact with a tag. |
lyfe/contacts/backfill | Build contacts from every completed order. --batch=N sets the batch size (default 500). |
lyfe/contacts/sync-stats | Refresh the order figures scheduled scans filter on. |
lyfe/contacts/unsubscribe <email> | Unsubscribe an address from email and SMS. |
All of them run as php craft <command>.
Extending Lyfe
Triggers, conditions, actions and SMS transports are registries keyed by a handle, so a module can add its own. Handles are what Lyfe stores, so your class can move without breaking saved workflows.
use justinholtweb\lyfe\events\RegisterTypesEvent;
use justinholtweb\lyfe\services\Types;
use yii\base\Event;
Event::on(Types::class, Types::EVENT_REGISTER_ACTIONS, function(RegisterTypesEvent $event) {
$event->types[] = MyAction::class;
});
The other events are Types::EVENT_REGISTER_TRIGGERS, Types::EVENT_REGISTER_RULES and
Sms::EVENT_REGISTER_TRANSPORTS. An action extends justinholtweb\lyfe\base\BaseAction and
implements handle(), displayName(), settingsFields() and run().