Zum Inhalt

Benachrichtigungen

Übersicht

Die BO-Engine betreibt zwei Benachrichtigungskanäle:

Kanal Zweck Technologie
Push-Notifications Gewinner informieren Web Push (VAPID)
E-Mail Re-Engagement-Kampagnen Resend API

Push-Notifications

Winner-Notifications

Datei: workers/notifyWinners.ts (98 Zeilen) Trigger: Nach Settlement (non-fatal)

Ablauf

// notifyWinners.ts:41-97
async function notifyWinners(dayId: number) {
  // 1. Gewinner mit 4+ Treffern finden
  const winners = await pg.query(`
    SELECT DISTINCT player, MAX(matches) AS best_match
    FROM tickets t
    JOIN ticket_tips tp ON tp.ticket_id = t.ticket_id
    WHERE day_id = $1 AND matches >= 4
    GROUP BY player
  `, [dayId]);

  // 2. Sprache aus User-Profil
  const user = await pg.query(
    `SELECT preferred_language FROM users WHERE wallet_address = $1`,
    [player]
  );

  // 3. Push senden
  await sendPushToUser(player, getPayload(bestMatch, locale));
}

Notification-Texte

// notifyWinners.ts:13-26
const messages = {
  en: {
    4: { title: "You won!", body: "You matched 4 numbers!" },
    5: { title: "Big Win!", body: "You matched 5 numbers!" },
    6: { title: "JACKPOT!", body: "You matched ALL 6 numbers!" }
  },
  de: {
    4: { title: "Gewonnen!", body: "Du hast 4 Richtige!" },
    5: { title: "Großer Gewinn!", body: "Du hast 5 Richtige!" },
    6: { title: "JACKPOT!", body: "Du hast ALLE 6 Richtige!" }
  }
};

Deep Link: /{locale}/claim - direkt zur Auszahlungsseite.

Push-Service

Datei: services/pushService.ts (73 Zeilen)

// pushService.ts:35-72
async function sendPushToUser(walletAddress: string, payload: object) {
  // 1. Alle Subscriptions des Users laden
  const subs = await pg.query(
    `SELECT endpoint, p256dh, auth
     FROM push_subscriptions
     WHERE wallet_address = $1`,
    [walletAddress]
  );

  // 2. Push an jedes Gerät senden
  for (const sub of subs.rows) {
    try {
      await webpush.sendNotification(sub, JSON.stringify(payload));
    } catch (e) {
      if (e.statusCode === 410 || e.statusCode === 404) {
        // Subscription abgelaufen → löschen
        await pg.query(
          `DELETE FROM push_subscriptions WHERE endpoint = $1`,
          [sub.endpoint]
        );
      }
    }
  }
}

Konfiguration

Variable Beschreibung
VAPID_PUBLIC_KEY VAPID Public Key
VAPID_PRIVATE_KEY VAPID Private Key
VAPID_SUBJECT mailto: oder URL

Auto-Cleanup

Abgelaufene Subscriptions (HTTP 410/404) werden automatisch aus der push_subscriptions-Tabelle entfernt.


E-Mail-System

E-Mail-Service

Datei: services/emailService.ts (67 Zeilen)

// emailService.ts:31-66
async function sendEmail(to, subject, html) {
  const config = await getEmailConfig(pg);

  const response = await fetch("https://api.resend.com/emails", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${config.resend_api_key}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      from: config.resend_from_email ?? "noreply@chainbets.win",
      to,
      subject,
      html
    })
  });
}

Konfiguration (bo_config)

Key Beschreibung Default
resend_api_key Resend API Key -
resend_from_email Absender-Adresse noreply@chainbets.win

E-Mail-Templates

Datei: services/emailTemplates.ts (250 Zeilen)

// emailTemplates.ts:38-142
function buildReEngagementEmail(params) {
  // Dark-Theme HTML-Template
  // Lokalisiert (EN/DE)
  // Platform-Stats integriert
  // Mobile-optimiert (MSO conditional)
  // "View in Browser" Link
}

Template-Features:

  • Dark Theme (passend zur App)
  • Plattform-Statistiken (7-Tage + Allzeit):
  • Tickets gespielt
  • Gewinne ausgezahlt
  • Anzahl Gewinner
  • Größter Gewinn
  • Hot/Cold Numbers
  • Bonus-Badge (wenn Bonus vergeben)
  • CTA-Button zum Login
  • "View in Browser"-Link mit Token

Telegram-Alerting (Watchdog)

Datei: watchdog/alerting/telegram.ts (41 Zeilen)

Systemwarnungen werden via Telegram-Bot gesendet:

// telegram.ts:8-41
async function sendTelegramAlert(message: string) {
  const url = `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`;

  await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      chat_id: CHAT_ID,
      text: message,
      parse_mode: "Markdown"
    })
  });
}
Variable Beschreibung
WATCHDOG_TELEGRAM_BOT_TOKEN Bot-Token
WATCHDOG_TELEGRAM_CHAT_ID Chat/Gruppe für Alerts

Cooldown-System: Verhindert Alert-Spam (CRITICAL: 5min, HIGH: 30min, MEDIUM: 120min).