All files / src/lib/monitoring alerts.ts

0% Statements 0/96
100% Branches 0/0
0% Functions 0/1
0% Lines 0/96

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97                                                                                                                                                                                                 
import { Resend } from 'resend';
import { logger } from '@/lib/logging';

const resend = process.env.RESEND_API_KEY ? new Resend(process.env.RESEND_API_KEY) : null;
const ALERT_EMAIL = process.env.ALERT_EMAIL || 'admin@example.com';
const ALERT_FROM = process.env.ALERT_FROM_EMAIL || 'alerts@elite-events.com';

// Rate limiting for alerts
const alertCooldowns = new Map<string, number>();
const COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes between same alerts

interface AlertData {
  timestamp: string;
  level: string;
  category: string;
  message: string;
  error?: unknown;
  context?: Record<string, unknown>;
}

export async function sendErrorAlertEmail(data: AlertData): Promise<void> {
  if (!resend) {
    logger.warn('Email alerts disabled: RESEND_API_KEY not configured', { category: 'SYSTEM' });
    return;
  }

  // Check cooldown to prevent alert spam
  const alertKey = `${data.category}:${data.message}`;
  const lastAlert = alertCooldowns.get(alertKey);
  if (lastAlert && Date.now() - lastAlert < COOLDOWN_MS) {
    return; // Skip, already sent recently
  }

  alertCooldowns.set(alertKey, Date.now());

  try {
    await resend.emails.send({
      from: ALERT_FROM,
      to: ALERT_EMAIL,
      subject: `[ALERT] ${data.category}: ${data.message.substring(0, 50)}`,
      html: `
        <div style="font-family: sans-serif; max-width: 600px;">
          <h2 style="color: #dc2626;">Error Alert</h2>

          <table style="width: 100%; border-collapse: collapse;">
            <tr>
              <td style="padding: 8px; border: 1px solid #ddd; font-weight: bold;">Time</td>
              <td style="padding: 8px; border: 1px solid #ddd;">${data.timestamp}</td>
            </tr>
            <tr>
              <td style="padding: 8px; border: 1px solid #ddd; font-weight: bold;">Category</td>
              <td style="padding: 8px; border: 1px solid #ddd;">${data.category}</td>
            </tr>
            <tr>
              <td style="padding: 8px; border: 1px solid #ddd; font-weight: bold;">Level</td>
              <td style="padding: 8px; border: 1px solid #ddd;">${data.level}</td>
            </tr>
            <tr>
              <td style="padding: 8px; border: 1px solid #ddd; font-weight: bold;">Message</td>
              <td style="padding: 8px; border: 1px solid #ddd;">${data.message}</td>
            </tr>
          </table>

          ${data.context ? `
            <h3>Context</h3>
            <pre style="background: #f5f5f5; padding: 12px; overflow-x: auto;">
${JSON.stringify(data.context, null, 2)}
            </pre>
          ` : ''}

          ${data.error ? `
            <h3>Error Details</h3>
            <pre style="background: #fef2f2; padding: 12px; overflow-x: auto; color: #dc2626;">
${data.error instanceof Error ? data.error.stack : String(data.error)}
            </pre>
          ` : ''}

          <p style="margin-top: 20px; color: #666;">
            <a href="${process.env.NEXT_PUBLIC_APP_URL}/admin/monitoring/errors">
              View Error Dashboard
            </a>
          </p>
        </div>
      `});
  } catch (error) {
    logger.error('Failed to send alert email', error instanceof Error ? error : new Error(String(error)), { category: 'SYSTEM' });
  }
}

// Daily summary email
export async function sendDailySummaryEmail(): Promise<void> {
  if (!resend) return;

  // This would be called by a cron job
  // Implementation depends on your cron setup
}