mailship/src/mailer.ts

99 lines
3.6 KiB
TypeScript
Raw Normal View History

2026-08-24 20:12:32 +00:00
import nodemailer from 'nodemailer'
2026-08-26 21:57:26 +00:00
import {
SMTP_HOST,
SMTP_PORT,
SMTP_USER,
SMTP_PASSWORD,
SMTP_FROM,
BASE_URL,
EVENT_VIEWER_URL,
BRAND_ACCENT,
BRAND_NAME,
BRAND_LOGO,
} from './env.js'
import type { Subscription } from './alert.js'
import { render } from './templates.js'
2026-08-25 19:15:33 +00:00
const secure = Number(SMTP_PORT) === 465
2026-08-24 20:12:32 +00:00
const transporter = nodemailer.createTransport({
host: SMTP_HOST,
port: Number(SMTP_PORT),
2026-08-25 19:15:33 +00:00
secure,
requireTLS: !secure,
2026-08-24 20:12:32 +00:00
auth: {
user: SMTP_USER,
pass: SMTP_PASSWORD,
},
})
export const sendConfirm = (sub: Subscription) => {
const href = `${BASE_URL}/confirm?token=${sub.key}`
2026-08-25 19:15:33 +00:00
return transporter
.sendMail({
from: SMTP_FROM,
to: sub.email,
2026-08-26 21:57:26 +00:00
subject: `Confirm your ${BRAND_NAME} digest`,
2026-08-25 19:15:33 +00:00
html: `
2026-08-26 21:57:26 +00:00
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background:#f0f2f5;padding:32px 16px;font-family:Inter,Helvetica,Arial,sans-serif;">
<tr>
<td align="center">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:480px;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e2e8f0;">
<tr>
<td style="padding:28px 32px 8px 32px;text-align:center;">
${BRAND_LOGO ? `<img src="${BRAND_LOGO}" alt="${BRAND_NAME}" style="height:40px;width:auto;max-width:200px;object-fit:contain;" />` : `<h1 style="margin:0;font-size:22px;color:#1e293b;font-weight:700;"><span style="color:${BRAND_ACCENT};">${BRAND_NAME}</span></h1>`}
<p style="margin:4px 0 0;color:#64748b;font-size:14px;">Digest notifications</p>
</td>
</tr>
<tr>
<td style="padding:16px 32px;color:#334155;font-size:15px;line-height:1.6;">
<p style="margin:0 0 12px;">Welcome to ${BRAND_NAME}!</p>
<p style="margin:0 0 12px;">Please confirm that you would like to receive ${sub.frequency} digests by clicking the button below:</p>
<p style="margin:0;text-align:center;">
<a href="${href}" style="display:inline-block;background:${BRAND_ACCENT};color:#ffffff;padding:12px 28px;border-radius:8px;font-weight:600;text-decoration:none;">Confirm Digest</a>
</p>
<p style="margin:16px 0 0;font-size:12px;color:#94a3b8;">Or visit: <a href="${href}" style="color:${BRAND_ACCENT};">${EVENT_VIEWER_URL}</a></p>
</td>
</tr>
</table>
</td>
</tr>
</table>
`,
2026-08-25 19:15:33 +00:00
text: `Please confirm that you would like to receive ${sub.frequency} digests by visiting: ${href}`,
})
.catch(error => {
console.error('mailer: confirmation email failed', {
to: sub.email,
smtp: { host: SMTP_HOST, port: SMTP_PORT },
error: error?.message || error,
})
throw error
})
}
export const sendDigest = async (sub: Subscription, variables: Record<string, any>) => {
2026-08-25 19:15:33 +00:00
return transporter
.sendMail({
from: SMTP_FROM,
to: sub.email,
subject: 'New activity',
html: await render('emails/digest.mjml', {
...variables,
name: sub.email.split('@')[0],
unsubscribeUrl: `${BASE_URL}/unsubscribe?token=${sub.key}`,
2026-08-26 21:57:26 +00:00
brandName: BRAND_NAME,
brandAccent: BRAND_ACCENT,
brandLogo: BRAND_LOGO,
2026-08-25 19:15:33 +00:00
}),
})
.catch(error => {
console.error('mailer: digest email failed', {
to: sub.email,
smtp: { host: SMTP_HOST, port: SMTP_PORT },
error: error?.message || error,
})
throw error
})
}