magendoo/module-merchant-pulse

Magendoo MerchantPulse — Store Health & Insights: scheduled digest emails and proactive alerts for Magento 2 merchants.

Maintainers

Package info

github.com/magendooro/magendoo-m2-merchantpulse

Type:magento2-module

pkg:composer/magendoo/module-merchant-pulse

Transparency log

Fund package maintenance!

magendoo.ro

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

2.0.0 2026-07-20 18:29 UTC

This package is auto-updated.

Last update: 2026-07-21 07:07:18 UTC


README

Store Health & Insights for Magento 2

Automated digest emails and proactive health alerts that keep you informed about what's happening in your store — catalog issues, system health, and recent activity — without having to log into the admin every day.

Screenshots

Digest email — health checks grouped into sections (Catalog Quality, Catalog Health, Index & Cron Health, Store Activity) with severity-coloured statuses, delivered on your schedule.

MerchantPulse digest email

Health Checks — review all 11 checks and run any of them on demand from the admin.

Health Checks admin page

Health Alerts — proactive, throttled alerts with a new → acknowledged → resolved lifecycle.

Health Alerts grid

Features

Scheduled Digest Reports

  • Daily, weekly, or monthly digest emails summarising store health
  • Configurable delivery time and day (weekly: day of week, monthly: day of month)
  • Four digest sections: Catalog Quality, Catalog Health, Index & Cron Health, Store Activity
  • HTML email rendered through Magento's email templates and sent via TransportBuilder
  • Each report saved to the database with full rendered HTML and JSON payload for audit

11 Health Checks

Category Code Severity What It Checks
Catalog stale_price warning Products whose catalog_product_entity.updated_at is older than N days. Caveat: this reflects the product's last edit, not price-change history — EAV value tables carry no timestamps, so a true "price hasn't changed" signal is not available.
Catalog missing_images warning Enabled products with no base image attribute (unset or no_selection), evaluated at store scope
Catalog missing_description info Enabled products with an empty description, evaluated at store scope
Catalog no_categories info Enabled products not assigned to any category
Catalog zero_price critical Enabled simple/virtual products with price ≤ 0
Catalog expired_special_price warning Products with a special_price set whose special_to_date is before today (UTC)
Catalog duplicate_sku critical Duplicate SKUs in the catalog
Catalog disabled_in_promo warning Disabled products referenced by an active catalog price rule (via catalogrule_product). Cart price rules (salesrule) target products through conditions rather than an enumerated product list and are not covered.
System index_health critical Indexers left in the invalid state (the transient working state is ignored)
System cron_health warning Cron jobs in an error / missed / stuck-running state within the configured Cron Lag window
Activity products_added info Count of products created during the digest period

Proactive Alerts

  • Severity levels: info, warning, critical (a plain string on each check)
  • Alert throttling to prevent notification floods (configurable hours)
  • Alerts stored in DB with full lifecycle: new → acknowledged → resolved
  • Newly fired alerts are also emailed to the configured recipients by the realtime cron
  • Admin grid with mass acknowledge / mass resolve actions

Admin UI

  • Merchant Pulse top-level menu in Magento Admin
  • Digest Reports grid with HTML preview
  • Health Alerts grid with severity filter and workflow actions
  • Health Checks page showing live check results
  • Full system configuration under Stores > Configuration > Magendoo > Merchant Pulse

Email Delivery

Emails are built and sent through Magento's own mail stack: Magento\Framework\Mail\Template\TransportBuilder renders the registered email templates (etc/email_templates.xmlview/adminhtml/email/digest.html and alert.html) and hands the message to the store's configured Magento mail transport. Because CLI and cron have no active area, the send is wrapped in store and admin-area emulation so the template loader and TransportBuilder have the store, locale, design, and area code they require. Delivery therefore honours whatever mail transport the Magento instance is configured to use — no custom SMTP client is involved.

Requirements

  • Magento Open Source / Adobe Commerce 2.4.6+ (tested on 2.4.9)
  • PHP 8.2, 8.3, or 8.4

Installation

Via Composer

composer require magendoo/module-merchant-pulse
bin/magento module:enable Magendoo_MerchantPulse
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

Manual

  1. Copy the module directory to app/code/Magendoo/MerchantPulse/
  2. Run:
bin/magento module:enable Magendoo_MerchantPulse
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

Configuration

Navigate to Stores > Configuration > Magendoo > Merchant Pulse.

General

Field Default Description
Enabled No Enable/disable the module
Digest Timezone UTC Timezone used for scheduling digest delivery. Leave empty to fall back to the store's locale timezone.

Digest Schedule

Field Default Description
Frequency Daily daily / weekly / monthly
Delivery Time 08:00 Time of day to send the digest (stored as 08,00,00)
Day of Week Monday (weekly only) Which day to send
Day of Month 1 (monthly only) Which day to send
Digest Sections catalog,catalog_health,index_health,activity Which sections to include

Recipients

Field Description
Recipient Email Addresses One email address per line (commas are also accepted)

Alerts

Field Default Description
Enable Alerts Yes Enable real-time check alerts
Alert Throttle (Hours) 24 Minimum hours between repeat alerts for the same check

Thresholds

Field Default Description
Stale Products (Days) 180 Days without a product update before it is flagged
Cron Lag Warning (Minutes) 60 Minutes overdue before cron is considered unhealthy

Quick local setup

bin/magento config:set merchant_pulse/general/enabled 1
bin/magento config:set merchant_pulse/recipients/email_list your@email.com
bin/magento cache:flush

Mail is delivered by Magento's configured transport; point that transport at a local mail catcher (MailHog / Mailpit) if you want to inspect messages during development.

CLI Commands

# Run all enabled checks and generate a digest report
bin/magento pulse:run --store=1 --type=daily

# Run without sending (saves report to DB, no email)
bin/magento pulse:run --store=1 --type=daily --dry-run

# Override the recipient for this run only
bin/magento pulse:run --store=1 --type=daily --email=you@example.com

# Send a digest with fine-grained period control (from/to, since-last-run, force)
bin/magento pulse:digest --store=1 --type=weekly --since-last-run

# Run a single check and print its result
bin/magento pulse:check zero_price --store=1
bin/magento pulse:check index_health --store=1 --format=json

# List all registered checks with severity and enabled status
bin/magento pulse:list-checks --store=1

# Send a test digest; --email overrides the configured recipients
bin/magento pulse:test --email=you@example.com --store=1

--email on pulse:run and pulse:test genuinely overrides the recipient list (it is passed through to DigestService::run() as the $recipientsOverride argument). pulse:test reports a non-zero exit and the stored error message when the send fails, rather than always claiming success.

Database Tables

Table Purpose
pulse_report Digest run records — stores status, rendered HTML, and JSON payload
pulse_alert Real-time health alerts with lifecycle tracking
pulse_digest_log Scheduling log — records the period window and the last successful send timestamp

Cron Jobs

The module ships its own cron group (etc/cron_groups.xml) so the custom group is scheduled independently of the default group.

Job Schedule Description
merchantpulse_digest_dispatcher Hourly Checks whether a digest is due (delivery hour/day evaluated in the configured Digest Timezone) and dispatches it
merchantpulse_realtime_checks Every 15 min Runs the checks, upserts alerts, and emails newly fired alerts

Extending with Custom Checks

Implement Magendoo\MerchantPulse\Api\CheckInterface and register the check in the CheckPool via di.xml:

// app/code/Vendor/Module/Model/Check/MyCustomCheck.php
namespace Vendor\Module\Model\Check;

use Magendoo\MerchantPulse\Api\CheckInterface;
use Magendoo\MerchantPulse\Api\Data\CheckResultInterface;
use Magendoo\MerchantPulse\Model\CheckResult;

class MyCustomCheck implements CheckInterface
{
    public function getCode(): string     { return 'my_custom_check'; }
    public function getLabel(): string    { return 'My Custom Check'; }
    public function getSeverity(): string { return 'warning'; } // 'info' | 'warning' | 'critical'
    public function isEnabled(int $storeId): bool { return true; }

    public function execute(int $storeId, \DateTimeInterface $from, \DateTimeInterface $to): CheckResultInterface
    {
        // ... your logic ...
        $failingItems = [];          // rows/details for the digest and alert
        $passed = $failingItems === [];

        // CheckResult(string $checkCode, string $severity, bool $passed,
        //             int $count = 0, array $items = [], string $message = '', array $details = [])
        return new CheckResult(
            'my_custom_check',
            'warning',
            $passed,
            count($failingItems),
            $failingItems,
            $passed ? 'All good.' : count($failingItems) . ' item(s) need attention.'
        );
    }
}
<!-- app/code/Vendor/Module/etc/di.xml -->
<type name="Magendoo\MerchantPulse\Model\Check\CheckPool">
    <arguments>
        <argument name="checks" xsi:type="array">
            <item name="my_custom_check" xsi:type="object">Vendor\Module\Model\Check\MyCustomCheck</item>
        </argument>
    </arguments>
</type>

Severity is a plain string — there are no SEVERITY_* or STATUS_* constants, and there is no getCategory() method.

Adding a Notification Channel

Only the Email channel ships with the module. To deliver digests and alerts elsewhere, implement Magendoo\MerchantPulse\Api\NotificationChannelInterface and register it in the ChannelPool:

interface NotificationChannelInterface
{
    public function sendDigest(string $renderedHtml, string $subject, array $recipients, int $storeId): void;
    public function sendAlert(array $alertData, array $recipients, int $storeId): void;
    public function getCode(): string;
    public function getLabel(): string;
    public function isAvailable(int $storeId): bool;
}
<!-- app/code/Vendor/Module/etc/di.xml -->
<type name="Magendoo\MerchantPulse\Model\Channel\ChannelPool">
    <arguments>
        <argument name="channels" xsi:type="array">
            <item name="my_channel" xsi:type="object">Vendor\Module\Model\Channel\MyChannel</item>
        </argument>
    </arguments>
</type>

Testing

The module ships a PHPUnit unit-test suite under Test/Unit/ covering the checks, the report builder/renderer/period calculator, the digest service, the email channel, the alert dispatcher, the repositories, and the config source models.

vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist app/code/Magendoo/MerchantPulse/Test/Unit

A set of legacy end-to-end Playwright specs also lives under Test/functional/ (cli.spec.js, admin.spec.js, email.spec.js). They exercise the CLI, admin grids, and captured email, and require a running Magento instance plus a local mail catcher (MailHog/Mailpit) — they are not part of the unit run.

Architecture

Magendoo_MerchantPulse/
├── Api/                         # Service contracts (interfaces)
│   ├── CheckInterface.php
│   ├── NotificationChannelInterface.php
│   ├── ReportRepositoryInterface.php
│   ├── AlertRepositoryInterface.php
│   └── Data/
├── Model/
│   ├── Check/
│   │   ├── AbstractCheck.php
│   │   ├── CheckPool.php        # DI-injected pool of all checks
│   │   ├── Catalog/             # 8 catalog health checks
│   │   ├── System/             # 2 system checks (indexer, cron)
│   │   └── Activity/           # 1 activity check (products added)
│   ├── Channel/
│   │   ├── ChannelPool.php      # DI-injected pool of notification channels
│   │   └── EmailChannel.php     # TransportBuilder + email templates, under store/area emulation
│   ├── Report/
│   │   ├── Builder.php          # Runs checks, assembles payload
│   │   ├── Renderer.php         # Renders payload to HTML email body
│   │   └── PeriodCalculator.php # Computes from/to window from digest log
│   ├── Alert/
│   │   └── Dispatcher.php       # Throttle-aware alert upsert
│   ├── Service/
│   │   └── DigestService.php    # Orchestrates build → render → save → send → log
│   └── Config.php               # All system config getters
├── Cron/
│   ├── DigestDispatcher.php     # Hourly — checks if a digest is due (Digest Timezone)
│   └── RunRealtimeChecks.php    # Every 15 min — fires and emails alerts
├── Console/Command/             # 5 CLI commands
├── Controller/Adminhtml/        # Admin controllers
├── Ui/                          # UI component data providers + column renderers
└── view/adminhtml/
    ├── ui_component/            # Grid XML definitions
    ├── layout/                  # Admin page layouts
    └── email/                   # digest.html + alert.html templates

Key Design Decisions

  1. Magento email templates via TransportBuilder — digests and alerts render through registered .html templates and are sent by the store's configured Magento mail transport. Because CLI/cron have no active area, the send is wrapped in admin-area code emulation plus store environment emulation so the template loader and TransportBuilder have a store, locale, design, and area code to work with.

  2. CheckPool / ChannelPool pattern — both pools are DI-injected arrays, making it trivial to add checks or channels from any third-party module without touching core code.

  3. Period calculation from pulse_digest_log — the PeriodCalculator reads the last successful send timestamp to compute a dynamic from/to window, so activity is not missed even when a cron run is skipped.

  4. Alert throttlingAlert\Dispatcher suppresses re-firing the same check alert for a configurable number of hours, preventing notification floods during extended incidents.

  5. Reports persist rendered HTML — the pulse_report table stores both the JSON payload and the final rendered HTML, so the admin can view exactly what was emailed at any time.

Licence

Open Software Licence 3.0 (OSL-3.0)

Author

Magendooflorinel.chis@gmail.com