Emails
This page lists the emails KahunaCart sends to customers and shows you how to enable, customize and test them. It is for the store administrator.
Before you begin
- Install and configure the Email plugin. Without it every email job throws
Email plugin is not availableand lands in the failed bucket. - Put Grav's scheduler in cron. Every email is queued rather than sent inline, and
bin/plugin kahunacart workdoes the sending. See Troubleshooting.
The emails KahunaCart sends
Every email is queued as a job, so nothing a customer or an administrator does waits on a mail server.
| Job type | Sent when | Templates | |
|---|---|---|---|
| Order confirmation | order.confirmation_email |
An order completes | order-confirmation.* |
| Payment received | order.payment_received_email |
An order awaiting payment becomes paid | payment-received.* |
| Shipped | order.shipped_email |
An order is marked as shipped | shipped.* |
| Refund | order.refund_email |
A provider confirms a refund | refund.* |
| Abandoned cart | cart.abandoned_sweep |
A cart sits untouched past cart.abandoned_hours |
abandoned-cart.* |
| Set password | customer.set_password_email |
A customer opts into an account at checkout, or a merchant sends an invitation | set-password.* |
Abandoned-cart reminders are off by default. The set-password email is sent only when store accounts are enabled.
The order confirmation is also BCC'd to the store when emails.store_copy_to is set. The other emails are not copied to the store.
When the payment-received email is sent
An instant-capture provider completes an order that is already paid, so no payment-received email is sent. The confirmation has already said everything this email would.
The email covers the offline case: a bank transfer completes the order with the payment pending, a merchant marks it paid days later, and this email tells the customer the money arrived. A redirect provider whose webhook lands after the customer leaves produces one too.
When the refund email is sent
The refund email is queued when the provider confirms a refund, never when one is submitted. A refund still awaiting confirmation sends nothing. See Orders → Refunds that are not over when you click.
The amount comes from the job payload rather than from the order, because an order can carry several refunds.
Tracking numbers in the shipped email
KahunaCart does not record a tracking number, so the shipped email does not carry one. Fulfillment is a single flag on the whole order — see Orders → Fulfillment status.
Note
A store that has tracking numbers can add them in a template override.
Each email sends once
Each email is queued at a transition that is already guarded against repeating: CheckoutService::complete() for the confirmation, CheckoutService::markPaid() for the payment-received email, a fulfill endpoint that refuses an order which is not unfulfilled, a transaction ledger whose markSuccess() returns false for a row that already succeeded, and a stamp written into the cart's data_json after an abandoned-cart send. A redelivered webhook or a double-clicked admin button queues nothing the second time. A job that throws is retried with exponential backoff, so a failed send is tried again and a successful one is never repeated.
Templates
Each email is a pair of Twig templates, HTML and plain text. Both are sent, and the recipient's mail client picks one.
templates/emails/order-confirmation.html.twig order-confirmation.txt.twig
templates/emails/payment-received.html.twig payment-received.txt.twig
templates/emails/shipped.html.twig shipped.txt.twig
templates/emails/refund.html.twig refund.txt.twig
templates/emails/abandoned-cart.html.twig abandoned-cart.txt.twig
templates/emails/set-password.html.twig set-password.txt.twig
On a multilanguage site each order email renders in the language the order was placed in: subject line, both templates and the money formatting. The set-password email is the exception, because a customer record carries no language. See Multilanguage.
Variables in the order emails
| Variable | What it is |
|---|---|
order |
The order row, including number, hash, currency and the status columns |
items |
Its line items, each with title, qty, unit_price_minor, is_digital |
adjustments |
Its adjustments — discounts, shipping, tax |
total |
The order total, already formatted |
site_name |
site.title, or empty |
The refund email adds three more: amount (this refund, formatted), amount_minor, and refunded_total (everything confirmed on the order so far).
Variables in the set-password email
| Variable | What it is |
|---|---|
customer_name |
The customer's name, or empty |
customer_email |
The address the account is keyed to, which is also their username |
set_password_path |
The site-relative reset URL. Prefix it with base_url_absolute |
expires |
Unix timestamp at which the link stops working |
site_name |
site.title, or empty |
Override an email template
A theme overrides an email template by name.
- Create a
templates/emails/directory in your theme. - Copy the template you want to change out of
user/plugins/kahunacart/templates/emails/into it. - Edit the copy.
- Repeat for the other half of the pair so the HTML and plain-text versions match.
The next send uses your version.
Turn on abandoned-cart reminders
- Set
cart.abandoned_enabledtotrue. - Set
cart.abandoned_hoursto the idle time a cart must reach. The default is24. - Optionally set
emails.abandoned_subjectto your own subject line. Leave it blank to use the translated default.
Reminders go out on the next worker tick. Each cart is mailed at most once.
Test that emails are sending
- Place a test order on the storefront.
- Run the worker by hand, then check the queue counts.
bin/plugin kahunacart work # run the queue by hand
bin/plugin kahunacart status # pending / failed / completed job counts
A failed job keeps its last_error. The most common cause of missing mail is a scheduler that was never installed in cron.
Translate the email strings
Every string in every template goes through |t. Subjects go through the language system too, with the order number as a positional argument so a translation can move it inside the sentence.
EMAIL_SHIPPED_SUBJECT: 'Order #%s is on its way'
Keys live under PLUGIN_KAHUNACART. in languages/en.yaml. The abandoned-cart subject is the one that can also be set in config, with emails.abandoned_subject.
Configuration reference
| Key | Default | Effect |
|---|---|---|
emails.store_copy_to |
empty | BCC address for order confirmations |
emails.abandoned_subject |
empty | Overrides the translated abandoned-cart subject |
cart.abandoned_enabled |
false |
Turns abandoned-cart reminders on |
cart.abandoned_hours |
24 |
How long a cart must sit idle before it earns a reminder |
The From address comes from the Email plugin's own from and from_name settings.