feat(#25): email notifications via CyberMail

- Notifier.php: CyberMail API sender with 4 trigger types (account
  created, suspended, disk quota warning, SSL expiry)
- Reads cybermail_api_key / notify_from_* / notify_admin_email from
  settings table
- accounts.php: fires Notifier on create (welcome + admin alert) and
  suspend (user + admin alert)
- system.php: notify-settings GET, save-notify-settings POST,
  test-notify POST (with API key masking)
- bin/notify-checks.php: daily cron for disk ≥85% and SSL ≤14 days
  (flag-based dedup in settings table)
- admin panel: Notifications page with form + trigger reference table;
  sidebar link added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 04:12:47 +00:00
parent 33c36ffc65
commit 2ab74b7569
6 changed files with 407 additions and 2 deletions
+10 -2
View File
@@ -12,6 +12,7 @@ require_once NOVACPX_LIB . '/AccountManager.php';
require_once NOVACPX_LIB . '/VhostManager.php';
require_once NOVACPX_LIB . '/DNSManager.php';
require_once NOVACPX_LIB . '/PHPManager.php';
require_once NOVACPX_LIB . '/Notifier.php';
// Resellers can only see their own accounts
$ownerId = $user['role'] === 'reseller' ? $user['uid'] : null;
@@ -82,14 +83,21 @@ match ($action) {
$result = AccountManager::create($body);
audit('account.create', $body['domain'], $result);
// Send welcome email to user + admin notification
Notifier::accountCreated(array_merge($body, ['email' => $body['email']]), $body['password']);
Response::success($result, 'Account created successfully');
})(),
'suspend' => (function() use ($db, $body, $ownerClause) {
$id = (int)($body['id'] ?? 0);
$acct = $db->fetchOne("SELECT a.id FROM accounts a JOIN users u ON u.id = a.user_id WHERE a.id = ? $ownerClause", [$id]);
$acct = $db->fetchOne(
"SELECT a.id, a.username, a.domain, u.email FROM accounts a JOIN users u ON u.id = a.user_id WHERE a.id = ? $ownerClause",
[$id]
);
if (!$acct) Response::error("Account not found", 404);
AccountManager::suspend($id, $body['reason'] ?? '');
$reason = $body['reason'] ?? '';
AccountManager::suspend($id, $reason);
Notifier::accountSuspended($acct, $reason);
audit('account.suspend', "account:$id");
Response::success(null, 'Account suspended');
})(),