Direct Answer
Contao's System-Notifications module has no built-in throttling, debouncing, or deduplication mechanism. The backend configuration does not expose any setting to collapse repeated events or enforce a cooldown window. Each qualifying event fires an individual email immediately via the event dispatcher.
Why This Happens
The module hooks into Contao's event dispatcher (Symfony's EventDispatcher component). Listeners registered for contao.system_notification (or similar internal events) receive every occurrence independently. There is no aggregation layer, rate limiter, or cache that groups events by type, severity, or time window before sending.
Available Methods to Limit Frequency
Since no configuration toggle exists, you must implement a custom solution. The practical approaches are:
- Custom event listener with in-memory or persistent deduplication cache
- Subscribe to the same notification event with a higher priority (or replace the default listener).
- Track recent events by a composite key (event type + severity + optional context like page ID or user ID).
- Enforce a configurable cooldown (e.g., 5 minutes) before allowing another email for the same key.
- Store state in Redis, database table, or filesystem cache so it survives process restarts.
- Batch aggregation via scheduled command
- Persist every notification event to a queue/table instead of sending immediately.
- Run a cron job (e.g., every 15 minutes) that groups pending events by type/severity and sends a single digest email.
- Requires replacing the default email-sending listener entirely.
- Third-party mail gateway filtering
- If you relay through a service like SendGrid, Postmark, or AWS SES, use their built-in suppression/deduplication rules (e.g., "max 1 email per recipient per template per hour").
- This is a workaround, not a Contao-level fix, but requires zero code changes.
Minimal Custom Listener Sketch (Conceptual)
// src/EventListener/ThrottledNotificationListener.php
namespace App\EventListener;
use Contao\CoreBundle\Event\SystemNotificationEvent;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
class ThrottledNotificationListener
{
private CacheInterface $cache;
private int $cooldownSeconds = 300; // 5 min
public function __construct(CacheInterface $cache)
{
$this->cache = $cache;
}
public function __invoke(SystemNotificationEvent $event): void
{
$key = sprintf(
'sys_notif_%s_%s_%s',
$event->getType(),
$event->getSeverity(),
$event->getContextHash() ?? 'global'
);
$this->cache->get($key, function (ItemInterface $item) use ($event) {
$item->expiresAfter($this->cooldownSeconds);
// Actually send the email here (call original mailer service)
$this->mailer->send($event->getNotification());
return true;
});
}
}
Register the listener with a priority higher than the default mailer listener so it intercepts first. Adjust $cooldownSeconds and the cache key composition to match your noise profile.
Verification Steps
- Open Contao backend → System → Notifications. Confirm no "throttle", "debounce", "digest", or "frequency" fields exist.
- Search the codebase for
SystemNotificationEvent or system_notification listeners to see the current dispatch path.
- Deploy the custom listener to staging, trigger a burst of events (e.g., bulk page publish), and verify only one email per cooldown window arrives.
One Diagnostic Detail Needed
What is the typical event volume and burst pattern in your environment (e.g., "50 page edits in 2 minutes during content imports")? This determines whether a simple cooldown cache suffices or you need batch digestion.