logo NodeSeekbeta

Komari美化通知邮件模板-更新2.0版本

优化前:
image

优化后:
image
image
image

前言:
需要使用发信服务商的API我用的是Mailgun,一天免费100封够用了。用其他api复制代码找gemini去改就行了,报错了就复制给gemini。自建邮局需要用docker自建一个SMTP-to-API。

步骤

  1. 请在代码顶部的 C 对象中填入您的 Mailgun 信息:
  • key: Mailgun API Key
    在哪里找:Mailgun 后台 -> Settings -> API Keys -> Private API Key。
    格式通常是一长串字符(如xxxxxxxxxx-xxxxxxxx...)。

  • dom: 您的发送域名
    在哪里找:Mailgun 后台 -> Sending -> Domains。
    例如:mg.yourdomain.com 或 notify.example.com。

  • from: 发件人名称与地址
    格式:"显示的名称 <邮箱地址>"。
    例如:"Komari Monitor [email protected]"。

  • to: 接收通知的邮箱
    您想让谁收到通知就填谁。

  1. Komari打开设置,通知,开启通知,通知渠道,JavaScript 代码填入保存就可以了。

Mailgun API版本

async function sendEvent(event) {
  const C = {
    key: "YOUR_MAILGUN_API_KEY",
    dom: "YOUR_MAILGUN_DOMAIN",
    from: "Komari Monitor <[email protected]>",
    to: "[email protected]"
  };

  const b64 = s => {
    try { if (typeof btoa != 'undefined') return btoa(s); } catch (e) {}
    try { if (typeof Buffer != 'undefined') return Buffer.from(s).toString('base64'); } catch (e) {}
    let o = "", c = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", i = 0, a, b, d;
    while (i < s.length) {
      a = s.charCodeAt(i++);
      b = s.charCodeAt(i++);
      d = s.charCodeAt(i++);
      o += c.charAt(a >> 2) + c.charAt((a & 3) << 4 | b >> 4) + c.charAt((b & 15) << 2 | d >> 6) + c.charAt(d & 63).replace(64, '=');
    }
    return o;
  };

  const send = async (html, sub) => {
    try {
      const resp = await fetch(`https://api.mailgun.net/v3/${C.dom}/messages`, {
        method: 'POST',
        headers: {
          'Authorization': `Basic ${b64('api:' + C.key)}`,
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: `from=${encodeURIComponent(C.from)}&to=${encodeURIComponent(C.to)}&subject=${encodeURIComponent(sub)}&html=${encodeURIComponent(html)}`
      });
      return resp.ok;
    } catch (e) { return false; }
  };

  try {
    const c = event.clients || [];
    const map = {
      'Offline': ['#e11d48', '离线警告'],
      'Online': ['#059669', '恢复上线'],
      'Alert': ['#d97706', '系统告警'],
      'Renew': ['#2563eb', '自动续费'],
      'Expire': ['#dc2626', '到期提醒'],
      'Test': ['#7c3aed', '测试通知']
    };
    const [color, title] = map[event.event] || ['#4b5563', '系统通知'];

    const d = new Date(new Date(event.time).getTime() + 28800000);
    const date = `${d.getUTCFullYear()}/${d.getUTCMonth() + 1}/${d.getUTCDate()} ${d.getUTCHours().toString().padStart(2, '0')}:${d.getUTCMinutes().toString().padStart(2, '0')}`;

    const rows = c.map(x => 
      `<tr><td style="padding:10px 0;border-bottom:1px solid #f3f4f6"><div style="display:inline-block;background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;padding:4px 8px"><span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:${color};margin-right:6px;margin-bottom:1px"></span><span style="font-weight:400;color:#1f2937;font-size:13px;letter-spacing:-0.2px">${x.name || '?'}</span></div></td><td style="text-align:right;border-bottom:1px solid #f3f4f6"><span style="font-size:10px;font-weight:500;color:#6b7280;background:#f9fafb;padding:3px 8px;border-radius:20px;font-family:'MiSans','MiLanPro',ui-monospace,SFMono-Regular,monospace;border:1px solid #e5e7eb">${x.region || ''}</span></td></tr>`
    ).join('');

    const html = `<div style="font-family:'MiSans','MiLanPro','MiLan',ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;background:#ffffff;padding:40px 0"><div style="width:94%;max-width:480px;margin:0 auto;background:#fff;border-radius:12px;border:0;border-top:4px solid ${color};box-shadow:inset 0 3px 6px rgba(0,0,0,0.06),0 0 0 1px #e5e7eb,0 10px 25px -5px rgba(0,0,0,0.18),0 8px 10px -6px rgba(0,0,0,0.1);overflow:hidden"><div style="padding:28px 24px"><h1 style="font-size:20px;margin:0;color:#111827;font-weight:600;letter-spacing:-0.5px">${title}</h1><p style="color:#6b7280;font-size:12px;margin:8px 0 20px 0;font-weight:500">${date}</p>${event.message ? `<div style="margin:20px 0;background:#f9fafb;border-radius:6px;padding:12px 15px;color:#4b5563;font-size:13px;line-height:1.6;border:1px solid #f3f4f6">${event.message}</div>` : ''}${rows ? `<div style="margin-top:20px"><table style="width:100%;border-collapse:collapse">${rows}</table></div>` : ''}</div><div style="background:#fafafa;padding:12px;text-align:center;font-size:10px;color:#9ca3af;border-top:1px solid #f3f4f6;font-weight:500;letter-spacing:0.5px">Komari Monitoring</div></div></div>`;

    let sub = `[${title}] ` + (!c.length ? 'System' : (c.length === 1 ? c[0].name : `${c.length} Servers: ${c.map(x => x.name).join(', ')}`));
    if (sub.length > 80) sub = sub.slice(0, 77) + '...';
    
    return await send(html, sub);

  } catch (e) {
    return await send(`Error: ${e.message}`, 'Script Error');
  }
}

用ai改了一份Resend API版本,自测

async function sendEvent(event) {
  const C = {
    key: "re_12345678_xxxxxxxxxxxxxxxxxxxxxxxx",
    from: "Komari Monitor <[email protected]>",
    to: "[email protected]"
  };

  const send = async (html, sub) => {
    try {
      const resp = await fetch('https://api.resend.com/emails', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${C.key}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          from: C.from,
          to: C.to,
          subject: sub,
          html: html
        })
      });
      return resp.ok;
    } catch (e) { return false; }
  };

  try {
    const c = event.clients || [];
    const map = {
      'Offline': ['#e11d48', '离线警告'],
      'Online': ['#059669', '恢复上线'],
      'Alert': ['#d97706', '系统告警'],
      'Renew': ['#2563eb', '自动续费'],
      'Expire': ['#dc2626', '到期提醒'],
      'Test': ['#7c3aed', '测试通知']
    };
    const [color, title] = map[event.event] || ['#4b5563', '系统通知'];

    const d = new Date(new Date(event.time).getTime() + 28800000);
    const date = `${d.getUTCFullYear()}/${d.getUTCMonth() + 1}/${d.getUTCDate()} ${d.getUTCHours().toString().padStart(2, '0')}:${d.getUTCMinutes().toString().padStart(2, '0')}`;

    const rows = c.map(x => 
      `<tr><td style="padding:10px 0;border-bottom:1px solid #f3f4f6"><div style="display:inline-block;background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;padding:4px 8px"><span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:${color};margin-right:6px;margin-bottom:1px"></span><span style="font-weight:400;color:#1f2937;font-size:13px;letter-spacing:-0.2px">${x.name || '?'}</span></div></td><td style="text-align:right;border-bottom:1px solid #f3f4f6"><span style="font-size:10px;font-weight:500;color:#6b7280;background:#f9fafb;padding:3px 8px;border-radius:20px;font-family:'MiSans','MiLanPro',ui-monospace,SFMono-Regular,monospace;border:1px solid #e5e7eb">${x.region || ''}</span></td></tr>`
    ).join('');

    const html = `<div style="font-family:'MiSans','MiLanPro','MiLan',ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;background:#ffffff;padding:40px 0"><div style="width:94%;max-width:480px;margin:0 auto;background:#fff;border-radius:12px;border:0;border-top:4px solid ${color};box-shadow:inset 0 3px 6px rgba(0,0,0,0.06),0 0 0 1px #e5e7eb,0 10px 25px -5px rgba(0,0,0,0.18),0 8px 10px -6px rgba(0,0,0,0.1);overflow:hidden"><div style="padding:28px 24px"><h1 style="font-size:20px;margin:0;color:#111827;font-weight:600;letter-spacing:-0.5px">${title}</h1><p style="color:#6b7280;font-size:12px;margin:8px 0 20px 0;font-weight:500">${date}</p>${event.message ? `<div style="margin:20px 0;background:#f9fafb;border-radius:6px;padding:12px 15px;color:#4b5563;font-size:13px;line-height:1.6;border:1px solid #f3f4f6">${event.message}</div>` : ''}${rows ? `<div style="margin-top:20px"><table style="width:100%;border-collapse:collapse">${rows}</table></div>` : ''}</div><div style="background:#fafafa;padding:12px;text-align:center;font-size:10px;color:#9ca3af;border-top:1px solid #f3f4f6;font-weight:500;letter-spacing:0.5px">Komari Monitoring</div></div></div>`;

    let sub = `[${title}] ` + (!c.length ? 'System' : (c.length === 1 ? c[0].name : `${c.length} Servers: ${c.map(x => x.name).join(', ')}`));
    if (sub.length > 80) sub = sub.slice(0, 77) + '...';
    
    return await send(html, sub);

  } catch (e) {
    return await send(`Error: ${e.message}`, 'Script Error');
  }
}
12
  • mark了

  • 优化后这个截图是TG里面的效果?不错哦

  • @bbj #2 不是TG这个是邮件的效果
    image

  • 好东西,收藏加鸡腿

  • 我也以为是tg呢

  • mark xhj003

  • 顶

  • 加了点阴影,更立体了。同时适配手机大小

  • 成功复刻,感谢大佬教程。
    wX8ZQTwQUTulzgPx1L2rhoQJz93tEDrG.webp

  • @likeli #9 可以,我已经用了一段时间了,没啥bug

12

你好啊,陌生人!

我的朋友,看起来你是新来的,如果想参与到讨论中,点击下面的按钮!

📈用户数目📈

目前论坛共有72287位seeker

🎉欢迎新用户🎉