Files
ihaleist/server/email.mjs
T
hikmettan 3e129c2988 feat: tüm mock veriyi kaldır + Resend e-posta entegrasyonu
- mockTenders.js silindi; referans listeleri (sektör/il/kurum) constants.js'e taşındı
- AppContext: tenders/liveStats/competitorWatchlist/vaultDocuments/notifications/
  savedFilters/apiKeys/scrapedCapabilities → gerçek/boş; sahte Math.random online
  sayacı kaldırıldı; competitor & belge kasası artık localStorage
- LiveStatsBar: gerçek alanlar (canlı havuz/yüklü/bugün/kalem/takibimdeki)
- AnalyticsDashboard: KPI'lar (katılım/kazanma oranı/kazanılan bedel) + aylık hacim
  + sektörel dağılım gerçek companyStats/monthlyStats/sectorStats'tan; ciro hedefi
  kullanıcı tarafından belirlenir; veri yoksa dürüst boş durum
- ContractSearch & Rakip Radar: gerçek firmStats
- analytics.js: companyStats/monthlyStats/parseDateTR eklendi
- Resend: server/email.mjs + /api/notify/email/config + /api/notify/email/test
  (auth'lu, kullanıcının kendi adresine); PwaSettings gerçek e-posta test kartı
2026-08-28 08:49:10 +03:00

74 lines
3.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Resend e-posta gönderimi — bağımlılık yok (Node 18+ built-in fetch).
*
* Env:
* RESEND_API_KEY — Resend API anahtarı (re_...). Yoksa e-posta kapalıdır.
* EMAIL_FROM — Gönderen adres. Üretimde Resend'de DNS (SPF/DKIM) ile
* doğrulanmış kendi domaininiz olmalı. Doğrulanana kadar
* Resend'in paylaşımlı test adresi kullanılır.
*
* Kaynak/Endpoint: POST https://api.resend.com/emails (Bearer auth)
*/
const RESEND_ENDPOINT = 'https://api.resend.com/emails';
export function emailConfigured() {
return Boolean(process.env.RESEND_API_KEY);
}
export function emailFrom() {
return process.env.EMAIL_FROM || 'İhaleist <onboarding@resend.dev>';
}
export async function sendEmail({ to, subject, html, text }) {
const key = process.env.RESEND_API_KEY;
if (!key) throw new Error('E-posta yapılandırılmamış (RESEND_API_KEY yok)');
if (!to || !subject || (!html && !text)) throw new Error('Eksik alan: to, subject ve html/text gerekli');
const res = await fetch(RESEND_ENDPOINT, {
method: 'POST',
headers: {
Authorization: `Bearer ${key}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ from: emailFrom(), 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}`);
}
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>
<td style="padding:8px 12px;border-bottom:1px solid #eef1f5;font-size:12px;color:#586074;white-space:nowrap">${escapeHtml(t.sehir || '')}</td>
<td style="padding:8px 12px;border-bottom:1px solid #eef1f5;font-size:12px;color:#586074;white-space:nowrap">${escapeHtml(t.tarih || '')}</td>
</tr>`
)
.join('');
return `<div style="font-family:Arial,Helvetica,sans-serif;max-width:640px;margin:0 auto">
<h2 style="color:#2563eb;margin:0 0 4px">İhaleist</h2>
<p style="color:#586074;font-size:14px;margin:0 0 16px">Merhaba ${escapeHtml(name || '')}, takip kriterlerinize uyan ihaleler:</p>
<table style="border-collapse:collapse;width:100%;border:1px solid #eef1f5;border-radius:8px;overflow:hidden">
<thead><tr style="background:#f6f8fb">
<th style="padding:8px 12px;text-align:left;font-size:11px;color:#586074">İhale</th>
<th style="padding:8px 12px;text-align:left;font-size:11px;color:#586074">İl</th>
<th style="padding:8px 12px;text-align:left;font-size:11px;color:#586074">Tarih</th>
</tr></thead>
<tbody>${rows || '<tr><td style="padding:12px;color:#8b93a4;font-size:13px" colspan="3">Yeni ihale yok.</td></tr>'}</tbody>
</table>
<p style="color:#8b93a4;font-size:11px;margin-top:16px">Bu e-posta İhaleist tarafından gönderildi. Bildirim tercihlerinizi uygulama ayarlarından değiştirebilirsiniz.</p>
</div>`;
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
}