Skip to content

Notifly in Drupal

In Drupal, the easiest approach is to create a custom module notifly_alerts — one file and an info.yml. No composer dependencies are required; the built-in \Drupal::httpClient() is used.

modules/custom/notifly_alerts/
├── notifly_alerts.info.yml
├── notifly_alerts.module
└── src/
└── Notifly.php

notifly_alerts.info.yml:

name: 'Notifly Alerts'
type: module
description: 'Push-уведомления через сервер Notifly.'
core_version_requirement: ^9 || ^10
package: Custom

src/Notifly.php:

<?php
namespace Drupal\notifly_alerts;
class Notifly {
public static function send(string $title, string $message, int $priority = 5): void {
$config = \Drupal::config('notifly_alerts.settings');
$url = $config->get('url') ?: getenv('NOTIFLY_URL');
$token = $config->get('token') ?: getenv('NOTIFLY_TOKEN');
if (!$url || !$token) {
return;
}
try {
\Drupal::httpClient()->post("$url/message?token=$token", [
'json' => [
'title' => mb_substr($title, 0, 200),
'message' => mb_substr($message, 0, 1500),
'priority' => $priority,
],
'timeout' => 5,
]);
} catch (\Throwable $e) {
// Never break the main Drupal request because of a notification.
}
}
}

notifly_alerts.module:

<?php
use Drupal\notifly_alerts\Notifly;
use Drupal\node\NodeInterface;
use Drupal\user\UserInterface;
use Drupal\Core\Logger\RfcLogLevel;
/**
* A new node has been published.
*/
function notifly_alerts_entity_insert($entity) {
if ($entity instanceof NodeInterface) {
Notifly::send(
"📝 Новый узел: {$entity->getTitle()}",
"Тип: {$entity->bundle()}\n" .
"Автор: {$entity->getOwner()->getAccountName()}\n" .
"URL: " . $entity->toUrl('canonical', ['absolute' => TRUE])->toString(),
4
);
}
}
/**
* User registration.
*/
function notifly_alerts_user_insert(UserInterface $account) {
Notifly::send(
"👤 Регистрация: {$account->getAccountName()}",
"Email: {$account->getEmail()}\n" .
"Roles: " . implode(', ', $account->getRoles()),
5
);
}
/**
* PHP / watchdog errors at level ERROR and above.
*/
function notifly_alerts_logger_factory_alter(&$factory) {
// see implementation below — a separate logger service is required
}

To catch all events, add a custom logger. Create notifly_alerts.services.yml:

services:
logger.notifly:
class: Drupal\notifly_alerts\Logger\NotiflyLogger
arguments: ['@logger.log_message_parser']
tags:
- { name: logger }

And src/Logger/NotiflyLogger.php:

<?php
namespace Drupal\notifly_alerts\Logger;
use Drupal\Core\Logger\LogMessageParserInterface;
use Drupal\Core\Logger\RfcLoggerTrait;
use Drupal\notifly_alerts\Notifly;
use Psr\Log\LoggerInterface;
class NotiflyLogger implements LoggerInterface {
use RfcLoggerTrait;
public function __construct(private LogMessageParserInterface $parser) {}
public function log($level, string|\Stringable $message, array $context = []): void {
// 0=Emergency, 3=Error.
if ($level > 3) return;
$vars = $this->parser->parseMessagePlaceholders($message, $context);
$body = strtr((string) $message, $vars);
Notifly::send(
sprintf('🐞 %s [%s]', $context['channel'] ?? 'php', match ((int)$level) {
0,1 => 'CRITICAL',
2 => 'CRITICAL',
3 => 'ERROR',
default => 'WARN',
}),
$body . "\n\nRequest: " . ($context['request_uri'] ?? '-'),
$level <= 2 ? 10 : 8
);
}
}

After adding — drush cr. All watchdog events at ERROR/CRITICAL/EMERGENCY levels will be sent to Notifly.

Create config/install/notifly_alerts.settings.yml:

url: 'https://your-notifly.example.com'
token: 'AGdjfk_L.dKe8q'

And a settings page in the admin area — that’s the standard Drupal ConfigForm pattern.

You can use the built-in cache.default:

$cache_id = 'notifly_throttle:' . md5($title);
if (\Drupal::cache()->get($cache_id)) return;
\Drupal::cache()->set($cache_id, 1, time() + 60);
Notifly::send(/* ... */);
  • Full visibility of watchdog in your pocket. No need to go to Reports → Recent log.
  • Nodes and registrations in real time. Useful for moderation.
  • A cheap alternative to external error services.
  • Separate Notifly channels for content and technical events.
  • Include the node/user ID in extras so the Notifly client can provide deep-linking.