Include complete digest batches and validate email transport outcomes

This commit is contained in:
2026-09-06 13:01:18 +03:00
parent 4c4ba0c4c8
commit cbfdd072d1
4 changed files with 40 additions and 8 deletions
+6 -3
View File
@@ -23,33 +23,36 @@ export function emailFrom() {
return process.env.EMAIL_FROM || 'İhaleist <onboarding@resend.dev>';
}
export async function sendEmail({ to, subject, html, text }) {
export async function sendEmail({ to, subject, html, text, from = emailFrom(), idempotencyKey }) {
const key = (process.env.RESEND_API_KEY || '').trim();
if (!emailConfigured()) {
throw new Error("E-posta yapılandırılmamış — Coolify'da RESEND_API_KEY değerine gerçek anahtarı (re_...) yapıştırın.");
}
if (!to || !subject || (!html && !text)) throw new Error('Eksik alan: to, subject ve html/text gerekli');
if(idempotencyKey!==undefined && (typeof idempotencyKey!=='string'||!idempotencyKey||idempotencyKey.length>256||/[\r\n]/.test(idempotencyKey)))throw new Error('Geçersiz e-posta istek kimliği.');
const res = await fetch(RESEND_ENDPOINT, {
signal:AbortSignal.timeout(30000),
method: 'POST',
headers: {
Authorization: `Bearer ${key}`,
'Content-Type': 'application/json',
...(idempotencyKey?{'Idempotency-Key':idempotencyKey}:{}),
},
body: JSON.stringify({ from: emailFrom(), to: Array.isArray(to) ? to : [to], subject, html, text }),
body: JSON.stringify({ from, to: Array.isArray(to) ? to : [to], subject, html, text }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(data?.message || `Resend hatası: HTTP ${res.status}`);
}
if(typeof data.id!=='string'||!data.id.trim())throw new Error('E-posta sağlayıcısı gönderim kimliği döndürmedi; gönderim sonucu belirsiz.');
return { id: data.id };
}
// İhale bildirimi için basit, markasız HTML gövdesi
export function tenderDigestHtml({ name, tenders }) {
const rows = (tenders || [])
.slice(0, 15)
.map(
(t) => `<tr>
<td style="padding:8px 12px;border-bottom:1px solid #eef1f5;font-size:13px;color:#0e1526">${escapeHtml(t.baslik || '')}</td>