WP Manifestindependent plugin directory
manifest / email / wp-notifications-hub

Notifications Hub

Enterprise WordPress notifications plugin with custom database tables, REST API endpoints, unread indicators, and scheduled email digests via corporate SMTP

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/redrofigt/wp-notifications-hub/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Notifications Hub for WordPress

Enterprise notifications plugin for WordPress intranets: a dedicated custom database table, REST API endpoints, unread indicators, per-type badge counts, and scheduled email digests through corporate SMTP.

Built to WordPress coding standards: prepared statements everywhere, capability checks, whitelisted query inputs, and index-backed queries designed for 100k+ notification rows.

Why a custom table (not post meta)

Concern wp_postmeta wp_nh_notifications
Composite index for inbox queries ❌ not possible ✅ user_inbox (user_id, is_read, created_at)
Unread badge aggregate ❌ full scan via meta_query ✅ covering-index GROUP BY
Idempotent webhook inserts ❌ ✅ dedupe_idx unique-style guard
Retention cleanup ❌ slow ✅ indexed DELETE on expires_at, daily cron

Full rationale, schema, and the non-trivial query (with EXPLAIN output) are documented in docs/non-trivial-query.md.

Features

  • Custom table created via dbDelta() with 4 purpose-built indexes
  • REST API under notifications-hub/v1: GET /inbox, GET /unread-summary, POST /mark-read
    • every route requires authentication and is scoped to the authenticated user — client-supplied user_id is never trusted
  • Unread bell shortcode [nh_unread_bell] with a 60-second background refresher and 99+ capping
  • Hourly digest cron batching users 100 at a time, sending only to opted-in users via wp_mail() (corporate SMTP)
  • De-duplication on user_id + type + source + source_id so retried HR/SharePoint webhooks never double-notify
  • Daily retention purge of expired / 90-day-old rows

Schema (summary)

CREATE TABLE wp_nh_notifications (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  type VARCHAR(32) NOT NULL DEFAULT 'general',
  title VARCHAR(191) NOT NULL,
  body TEXT NULL,
  link VARCHAR(255) NULL,
  source VARCHAR(64) NOT NULL DEFAULT 'system',
  source_id BIGINT UNSIGNED NULL,
  is_read TINYINT(1) NOT NULL DEFAULT 0,
  read_at DATETIME NULL,
  emailed TINYINT(1) NOT NULL DEFAULT 0,
  expires_at DATETIME NULL,
  created_at DATETIME NOT NULL,
  PRIMARY KEY (id),
  KEY user_inbox (user_id, is_read, created_at),
  KEY type_created (type, created_at),
  KEY cleanup_idx (expires_at),
  KEY dedupe_idx (user_id, type, source, source_id)
);

Usage (PHP)

use NotificationsHub\Store;

// Notify one employee (de-duplicated by source_id).
Store::push( array(
    'user_id'   => 42,
    'type'      => 'policy',
    'title'     => 'New leave policy published',
    'link'      => 'https://intranet.example.com/policies/leave-2026',
    'source'    => 'sharepoint',
    'source_id' => 99123,
) );

// Broadcast to a department (chunked inserts).
Store::broadcast( $department_user_ids, array(
    'type'  => 'event',
    'title' => 'Town hall moved to 14:00',
) );

// Badge data: total + per-type counts in one query.
$summary = Store::get_unread_summary( get_current_user_id() );
// [ 'total' => 7, 'by_type' => [ 'news' => 4, 'hr' => 2, 'policy' => 1 ] ]

Security notes

  • All SQL through $wpdb->prepare(); IN() lists are built from absint-ed values with whitelisted placeholders
  • Notification types are whitelisted before ever reaching SQL
  • mark_read enforces ownership in the UPDATE's WHERE clause (user_id = %d) — IDOR-safe by construction
  • REST responses pass rows through a whitelist hydrator; no raw DB columns leak

Requirements

  • WordPress 6.4+, PHP 8.1+
  • Any SMTP plugin (or mu-plugin) pointing wp_mail() at the corporate relay