deployecommerce/module-order-status-email

Send any email template to the customer when an order reaches a chosen status. Rules are managed in the admin, scoped per website or store view, with a subject and template per rule.

Maintainers

Package info

github.com/DeployEcommerce/module-order-status-email

Homepage

Type:magento2-module

pkg:composer/deployecommerce/module-order-status-email

Transparency log

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-08-07 11:29 UTC

This package is auto-updated.

Last update: 2026-08-07 11:30:50 UTC


README

Send any email template to the customer when an order reaches a chosen status.

Magento can send an email when an order is placed, invoiced, shipped or credited. It cannot send one when an order reaches your status — the "Dispatched", "Ready for Collection" or "Awaiting Survey" that your business actually runs on. This module fills that gap with rules you manage in the admin rather than code.

composer require deployecommerce/module-order-status-email
bin/magento module:enable DeployEcommerce_OrderStatusEmail
bin/magento setup:upgrade

Rules

Marketing → Communications → Order Status Emails

Each rule says: in this scope, when an order reaches this status, send this template with this subject.

Field Notes
Scope Default, a website, or a store view
Order Status Every status defined on your store, read from the database — including your own
Email Template Anything under Marketing → Email Templates, or a template registered by a module
Subject The email's subject line. Also labels the rule in the grid, e.g. "Dispatch Confirmation"
Send From Which store email identity to send as
Active Turn a rule off without deleting it
Sort Order The order rules sharing a scope and status are queued in

How scope is decided

The most specific match wins outright, the same way store configuration works. A store view rule beats a website rule, which beats a default rule — and only the winning tier is sent.

That is what makes "turn this on for the US store only" a single row. If every matching scope fired instead, adding a default rule later would silently start double-emailing the US store.

Several rules may share the winning tier, and all of them fire, in sort order. Two emails at one status is a legitimate thing to want.

A rule with no template sends nothing

Leaving the template as Do Not Send keeps the rule and its history but sends no email. This is checked when the email is queued and again when it is sent, so clearing a template stops emails that are already in the queue.

Templates

Any template works. Every one gets the same variables:

Variable
order, order_id, store The order and its store
order_data.* customer_name, customer_email, customer_is_guest, is_not_virtual, email_customer_note, frontend_status_label
billing, formattedBillingAddress, formattedShippingAddress Named as core's sales emails name them, so core markup can be pasted straight in
shipment, shipments, tracks May be empty — see below
email_subject The subject from the rule
rule_status The status that triggered the email
year The current year

Variables are assembled inside store emulation for the order's store view, so locale, currency, address format and theme all resolve correctly even though the email is sent from cron.

Two things to get right in your template

The subject. Put {{var email_subject}} in the template's own subject line so the rule's subject is used:

<!--@subject {{var email_subject}} @-->

Without it the template's fixed subject wins and the rule's subject only labels the grid.

Shipments can be missing. An order can reach a "dispatched" status with no shipment record — because someone set the status by hand, or an import wrote it. Always guard:

{{depend shipment}}
    {{layout handle="sales_email_order_shipment_track" shipment=$shipment order=$order area="frontend"}}
{{/depend}}

A worked example covering all of this ships at view/frontend/email/order_status_change.html, registered as Order Status Change (example). Copy it under Marketing → Email Templates and edit your copy there.

Adding your own variables

<type name="DeployEcommerce\OrderStatusEmail\Model\TemplateVars\CompositeProvider">
    <arguments>
        <argument name="providers" xsi:type="array">
            <item name="loyalty" sortOrder="30" xsi:type="object">My\Module\Model\LoyaltyVars</item>
        </argument>
    </arguments>
</type>

Implement DeployEcommerce\OrderStatusEmail\Api\TemplateVarsProviderInterface. A provider that throws costs you its own variables and is logged; the email still goes out.

There is also an event, deployecommerce_order_status_email_set_template_vars_before, if you need to change variables for one specific order rather than for every email.

How sending works

An order status change queues a row; a cron job every minute sends it. Nothing is sent inside the request that changed the status.

That matters more than the minute of latency costs you. Sending inline would put the mail provider's HTTP latency inside admin saves, inside payment webhook handlers — which time out and retry the whole webhook — and inside checkout. It would also mean a provider outage silently loses emails, because a failure at that point in the request has nowhere to go but the log.

Instead:

  • The queue row is written inside the order's own database transaction, so it exists if and only if the status change was committed.
  • A unique key over (order_id, rule_id) makes duplicates impossible — across concurrent web nodes, a double-clicked admin save, or a redelivered payment webhook.
  • Failures are retried, up to Maximum Send Attempts, and keep their last error.
  • Everything is re-validated at send time. If the order moved back out of the status, or the rule was turned off, the row is marked skipped rather than sent.

One email per order per rule, permanently. An order that leaves the status and returns to it does not produce a second email.

Configuration

Stores → Configuration → Sales → Order Status Emails

Setting Default
Enabled Yes Per-scope kill switch, without editing rules
Send Copy To Comma-separated. Sent as separate emails, not blind copies, because some transports forward only the first BCC address
Emails Per Cron Run 50
Maximum Send Attempts 3
Keep Sent Records For 90 days Only sent rows are pruned; failed and skipped rows are kept

Requirements

Requires deployecommerce/module-order-status-transition, which does the work of noticing a status change reliably. See its README for what is and is not detected — in particular, status changes written straight to the database by bulk import tools are invisible to any event-based approach.

Development

composer install
composer test

Tests are written with Pest and need no Magento installation. Magento's own packages come from the public Mage-OS mirror, so no Adobe credentials are required.

To work on this and the transition package together, point Composer at your local checkout:

composer config repositories.transition path ../module-order-status-transition

That is a local convenience only — do not commit it.

Licence

MIT. See LICENSE.md.