Orange Collar Media
Orange Collar Media
DOCS
OCMLABS/MODULE-PRODUCT-MESSAGING · ARCHITECTURE

How Product Messaging is built - observers, the send queue, matching, and schema

  • Magento
  • Adobe Commerce
  • Hyvä

Module OCMLabs_ProductMessaging, namespace OCMLabs\ProductMessaging, Composer package ocmlabs/module-product-messaging, plus the ocmlabs/module-product-messaging-commerce Adobe Commerce licensing metapackage. Declarative schema only.

Design rule: observers never send

Observers never render templates, send mail, or resolve CMS content. They match SKUs against cached rules and insert queue rows, which are cheap database writes. A cron job does the sending. Any exception in the matching path is logged to var/log/ocmlabs_productmessaging.log (a dedicated Monolog handler) and swallowed, so a misconfigured rule cannot break checkout, invoicing, or shipping, and one unmatchable product or bad rule does not stop the other rules on the same order from queueing.

Order lifecycle observers

EventTrigger
checkout_submit_all_afterOrder Placed (single and multi-address checkout)
sales_order_invoice_save_afterInvoice Created
sales_order_shipment_save_afterShipment Created, matched against the shipment’s items only

Each observer loads active rules for its trigger, applies store view and customer group scope, matches candidate SKUs, and inserts a row with scheduled_at set to now plus the rule’s delay. A duplicate insert for the same rule and order is a no-op.

Matching engine

SKU matching is a framework-independent class: patterns are split on newlines and commas, trimmed, and compared case-insensitively, as exact strings or with * as the only wildcard. A separate extractor resolves candidate SKUs for an order or shipment, including child and parent SKUs when the rule’s match-children flag is on, and skips fully cancelled or refunded lines.

The same matcher and extractor serve both the email observers and the success-page resolver, so there is one matching implementation. Active rules are cached, indexed by trigger event and by success-block participation, and the cache is invalidated on rule save and delete. The matcher sits behind an interface so a condition-tree matcher can be added later without touching the rest of the module.

Send cron

ocmlabs_productmessaging_send (every minute, default group) loads pending rows with scheduled_at <= now, up to the batch size, and processes each in its own try/catch. For each row it:

  1. Emulates the order’s frontend store and area, so locale, currency, and theme-scoped template resolution match the order.
  2. Reloads the order and builds the template variables from it plus the row’s matched-item snapshot.
  3. Attaches the rule’s PDFs and sends through an extended TransportBuilder that adds attachment support on top of core mail transport.
  4. On failure, increments attempts and reschedules with retry_delay * 2^(attempts-1) (capped at 24 hours) until max_attempts, then marks the row failed. A missing template fails immediately.

Rows for orders cancelled or refunded while waiting are not sent. ocmlabs_productmessaging_prune (daily, 2:45 AM) deletes old sent, failed, and cancelled rows.

Success-page resolver

On checkout_onepage_success and the multishipping success page, a block resolver loads the session’s last real order (or orders, for multishipping), runs the same matcher against rules with the success-block action on, and renders each matched CMS block through the CMS template filter, in rule ID order. Nothing is queued or stored, and any failure renders nothing.

Database

TablePurpose
ocmlabs_productmessaging_ruleRule definitions: name, status, SKU patterns, match-children flag, the send_email and show_success_block toggles and their fields (trigger, template ID, delay, copy-to, copy method, success block), store and customer group scope, timestamps. Indexed on status and trigger_event.
ocmlabs_productmessaging_rule_attachmentPDF attachments per rule: path relative to pub/media/ocmlabs_productmessaging/, original file name, MIME type, size. ON DELETE CASCADE from the rule.
ocmlabs_productmessaging_emailQueue and log in one table: rule ID and name, order ID and increment ID, store ID, recipient, matched-items JSON snapshot, status, scheduled and sent timestamps, attempts, error. Unique key on (rule_id, order_id).

Configuration access

All settings are read through Model\Config, a typed reader over ScopeConfigInterface at store scope that clamps numeric values, so values written with config:set or directly to core_config_data cannot disable retries, remove the batch limit, or wipe the log.

Admin and ACL

Menu entries sit under Sales > Product Messaging. ACL resources are OCMLabs_ProductMessaging::manage (with child ::manage_delete), ::log, and ::config, all under Magento_Sales::sales. The template quick-create additionally requires Magento’s email-template ACL. All admin and storefront strings are in i18n/en_US.csv.