Files
kolaytercih/src/lib/eposta-gonder.ts
bilalgursen 5992cf3637 fix(eposta): Reply-To + prod'da sessiz fallback yok
- Resend gönderimine replyTo eklendi (EMAIL_REPLY_TO, yoksa
  destek@kolaytercih.com) — mail.kolaytercih.com'un MX'i olmadığı için
  yanıtlar bounce oluyordu.
- epostaKonsolModu(): konsola yazma yalnızca anahtar yokken VE (dev'de ya
  da EPOSTA_KONSOL_FALLBACK=1 ile) geçerli. Production'da anahtar yoksa
  artık "gönderildi" dönmüyor, token'lı link loga yazılmıyor; gönderim
  anında net hata fırlatılıyor (better-auth 500 → formda hata toast'ı).
- Production'da EMAIL_FROM yoksa sessizce onboarding@resend.dev'e düşmek
  yerine hata. Env okumaları çağrı anında — next build etkilenmez.

Canlı giriş yolunu değiştirir; merge öncesi lokal docker testi şart.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 04:49:08 +03:00

76 lines
3.0 KiB
TypeScript
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.
import "server-only";
import type { EpostaIcerik } from "./eposta";
/**
* Ortak e-posta göndericisi (Resend). Şablonlar eposta.ts'te, gönderim
* burada — magic link (auth.ts) ve kredi hatırlatması aynı yoldan çıkar.
*
* Env okumalarının tamamı ÇAĞRI ANINDA yapılır, modül seviyesinde değil:
* `next build` NODE_ENV=production + env'siz koşar; modül yüklenirken hata
* fırlatmak build'i kırar.
*/
/** Maile yanıt verilirse düşeceği adres — mail.kolaytercih.com'un MX'i yok. */
const VARSAYILAN_YANIT_ADRESI = "destek@kolaytercih.com";
/** Prod'da konsol fallback'ini bilinçli açan kaçış bayrağı (lokal docker testi). */
function kacisBayragi(): boolean {
return process.env.EPOSTA_KONSOL_FALLBACK === "1";
}
/**
* Konsol modu: gönderim yerine içerik terminale yazılır. Yalnızca Resend
* anahtarı yokken ve (dev'de ya da EPOSTA_KONSOL_FALLBACK=1 ile) geçerlidir.
* Production'da anahtar yoksa konsol moduna DÜŞÜLMEZ — giriş token'ı taşıyan
* link prod loglarına yazılmasın, kullanıcı da "gönderildi" sanmasın diye
* epostaGonder net hata fırlatır.
*/
export function epostaKonsolModu(): boolean {
return (
!process.env.RESEND_API_KEY &&
(process.env.NODE_ENV !== "production" || kacisBayragi())
);
}
export async function epostaGonder(
to: string,
icerik: EpostaIcerik,
): Promise<void> {
if (epostaKonsolModu()) {
console.log(
`\n[eposta] (konsol modu) ${to} adresine gönderilecekti — ${icerik.subject}\n${icerik.text}\n`,
);
return;
}
const anahtar = process.env.RESEND_API_KEY;
if (!anahtar) {
throw new Error("[eposta] RESEND_API_KEY tanımlı değil");
}
// EMAIL_FROM yoksa resend.dev adresi yalnızca Resend hesap sahibine teslim
// eder — prod'da sessizce buna düşmek yerine yapılandırma hatasını göster.
const gonderen = process.env.EMAIL_FROM;
if (!gonderen && process.env.NODE_ENV === "production" && !kacisBayragi()) {
throw new Error("[eposta] EMAIL_FROM tanımlı değil");
}
const { Resend } = await import("resend");
const resend = new Resend(anahtar);
const { error } = await resend.emails.send({
from: gonderen || "KolayTercih <onboarding@resend.dev>",
to,
// `||`: CI'da secret tanımsızsa env boş string gelir; boş Reply-To ile
// Resend'e gitmek yerine varsayılana düş.
replyTo: process.env.EMAIL_REPLY_TO || VARSAYILAN_YANIT_ADRESI,
subject: icerik.subject,
html: icerik.html,
// Düz metin alternatifi spam puanını düşürür ve HTML'i engelleyen
// istemcilerde içeriğin okunabilir kalmasını sağlar.
text: icerik.text,
});
// Resend SDK hata fırlatmaz, { data, error } döndürür. Kontrol etmezsek
// doğrulanmamış domain/kota hatası sessizce yutulur — fırlatarak çağıran
// tarafın (better-auth, hatırlatma taraması) haberdar olmasını sağlıyoruz.
if (error) {
console.error(`[eposta] Gönderilemedi (${to}):`, error);
throw new Error(`Resend: ${error.name} — ${error.message}`);
}
}