Publish from your module

For arlanix/notification 1.0.0

Publish from your module

A module publishes a message to a topic when something happens. The store chooses the topic in your module's settings; who gets it is up to the topic's subscriptions.

To leave what is sent, when and where to the store's rules instead, declare events: a rule then answers yours as it answers a new order. Publish yourself for what your module's own settings decide, such as an export's failure alerts.

The message

Field Type
subject string One line: line breaks and runs of spaces become one space. Up to 255 characters; longer is cut, ending with ….
body string Plain text, up to 65,536 characters, cut the same way.
severity info, warning or error Anything else is info. A subscription takes everything, warnings and errors, or errors only.
attributes array<string, scalar> Facts shown beside the body, such as ['Export' => 'orders-nightly'], in the order given: up to 50, each label up to 64 characters and each value up to 1,024. true and false show as yes and no; an array, an object, null, or an empty label or value is left out.
source string Your module, such as Vendor_Module, up to 128 characters. The Message Log shows it.
details bool true by default: below the body come the attributes, the topic, the source and the time. false sends the subject and the body alone, for a body that says it all.

Deciding whether something is worth a message is yours: publish when a state changes (an export starts failing, then works again), not on every run. The same message published again within a batch is merged into one entry with its count; one published every minute is still sent every minute.

With the module required

Require arlanix/module-notification in composer.json and Arlanix_Notification in module.xml's sequence, then:

use Arlanix\Notification\Api\Data\MessageInterfaceFactory;
use Arlanix\Notification\Api\PublisherInterface;

public function __construct(
    private readonly PublisherInterface $publisher,
    private readonly MessageInterfaceFactory $messageFactory
) {
}

public function announce(int $topicId, string $export, string $reason): void
{
    $this->publisher->publish($topicId, $this->messageFactory->create([
        'subject' => sprintf('Export %s failed', $export),
        'body' => $reason,
        'severity' => 'error',
        'attributes' => ['Export' => $export],
        'source' => 'Vendor_Module',
    ]));
}

publish() returns how many deliveries it queued and never throws: a missing topic is logged as a warning, a switched-off one as info, anything else as an error, and your code goes on. It writes to the database and the queue, and does not wait for a mail server, unless it is told to send at once, below.

Inside a database transaction, the message is written in it and its deliveries go once it commits: a rollback takes the message back and sends nothing. To tell of the failure that rolls a transaction back, publish after the rollback.

At once, past the queue

$this->publisher->publish($topicId, $message, true);

The third argument, $immediate, sends the message in the same process instead of queueing it, once the transaction it is in commits, and waits for each subscription's mail server or chat, up to its timeout of 10 seconds. It is for a message about what stops the queue itself, as monitoring's Cron stopped is; a delivery that fails is retried by the cron as any other. A declared event does the same with immediate="true": see Declare events. The event arlanix_notification_publish always queues.

With the module only suggested

Put it under suggest in composer.json, and publish through an event, which needs none of its classes:

$this->eventManager->dispatch('arlanix_notification_publish', [
    'topic_id' => $topicId,
    'subject' => sprintf('Export %s failed', $export),
    'body' => $reason,
    'severity' => 'error',
    'attributes' => ['Export' => $export],
    'source' => 'Vendor_Module',
]);

Without Arlanix Notifications installed, nobody listens and nothing happens. A topic_id of 0, which a select left on -- Do not send -- stores, publishes nothing. The event takes every field of the message but details: what it publishes always has its details.

The topic setting

Topics are made by the store, so your settings let it choose one. With the module required, use its source model in system.xml:

<field id="alerts_topic" translate="label" type="select" sortOrder="10" showInDefault="1">
    <label>Send alerts to topic</label>
    <source_model>Arlanix\Notification\Model\Source\TopicsOrNone</source_model>
</field>

TopicsOrNone lists every topic, a switched-off one marked so, after -- Do not send --.

With the module only suggested, name none of its classes: a missing class would break the configuration page. Hide the field while the module is not installed, and fill it through an event with a source model of your own:

<field id="alerts_topic" translate="label" type="select" sortOrder="10" showInDefault="1">
    <label>Send alerts to topic</label>
    <source_model>Vendor\Module\Model\Source\NotificationTopics</source_model>
    <if_module_enabled>Arlanix_Notification</if_module_enabled>
</field>
class NotificationTopics implements \Magento\Framework\Data\OptionSourceInterface
{
    public function __construct(private readonly \Magento\Framework\Event\ManagerInterface $eventManager)
    {
    }

    public function toOptionArray(): array
    {
        $transport = new \Magento\Framework\DataObject();
        $this->eventManager->dispatch('arlanix_notification_topic_options', ['transport' => $transport]);

        return [['value' => '', 'label' => __('-- Do not send --')], ...(array)$transport->getData('options')];
    }
}

From outside Magento

bin/magento arlanix:notification:publish publishes from a script or a monitoring system: see Queue, cron and command line.