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>
This commit is contained in:
@@ -5,7 +5,7 @@ import { magicLink } from "better-auth/plugins";
|
|||||||
import { appDb, schema } from "./appdb";
|
import { appDb, schema } from "./appdb";
|
||||||
import { grantCredits, DENEME_KREDISI } from "./credits";
|
import { grantCredits, DENEME_KREDISI } from "./credits";
|
||||||
import { magicLinkEpostasi } from "./eposta";
|
import { magicLinkEpostasi } from "./eposta";
|
||||||
import { epostaGonder } from "./eposta-gonder";
|
import { epostaGonder, epostaKonsolModu } from "./eposta-gonder";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify URL'ini maile konacak onay sayfası URL'ine çevirir. Doğrulama linki
|
* Verify URL'ini maile konacak onay sayfası URL'ine çevirir. Doğrulama linki
|
||||||
@@ -30,13 +30,15 @@ async function sendMagicLinkEmail(email: string, url: string) {
|
|||||||
(globalThis as { __sonMagicLink?: string }).__sonMagicLink = url;
|
(globalThis as { __sonMagicLink?: string }).__sonMagicLink = url;
|
||||||
}
|
}
|
||||||
const mailLinki = onaySayfasiUrl(url);
|
const mailLinki = onaySayfasiUrl(url);
|
||||||
if (!process.env.RESEND_API_KEY) {
|
if (epostaKonsolModu()) {
|
||||||
// Dev fallback: Resend anahtarı yoksa linki terminale yaz
|
// Konsol modu (dev ya da EPOSTA_KONSOL_FALLBACK=1): linki terminale yaz.
|
||||||
|
// Prod'da anahtar yoksa buraya girilmez; epostaGonder hata fırlatır —
|
||||||
|
// token'lı link prod loglarına düşmez.
|
||||||
console.log(`\n[giris] Magic link for ${email}:\n${mailLinki}\n`);
|
console.log(`\n[giris] Magic link for ${email}:\n${mailLinki}\n`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Gönderim hatası better-auth'a fırlatılır — aksi halde kullanıcı
|
// Gönderim/yapılandırma hatası better-auth'a fırlatılır — aksi halde
|
||||||
// "gönderildi" ekranını görür ama mail hiç çıkmaz.
|
// kullanıcı "gönderildi" ekranını görür ama mail hiç çıkmaz.
|
||||||
await epostaGonder(email, magicLinkEpostasi(mailLinki));
|
await epostaGonder(email, magicLinkEpostasi(mailLinki));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,23 +4,62 @@ import type { EpostaIcerik } from "./eposta";
|
|||||||
/**
|
/**
|
||||||
* Ortak e-posta göndericisi (Resend). Şablonlar eposta.ts'te, gönderim
|
* Ortak e-posta göndericisi (Resend). Şablonlar eposta.ts'te, gönderim
|
||||||
* burada — magic link (auth.ts) ve kredi hatırlatması aynı yoldan çıkar.
|
* burada — magic link (auth.ts) ve kredi hatırlatması aynı yoldan çıkar.
|
||||||
* RESEND_API_KEY yoksa dev fallback: içerik terminale yazılır.
|
*
|
||||||
|
* 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(
|
export async function epostaGonder(
|
||||||
to: string,
|
to: string,
|
||||||
icerik: EpostaIcerik,
|
icerik: EpostaIcerik,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!process.env.RESEND_API_KEY) {
|
if (epostaKonsolModu()) {
|
||||||
console.log(
|
console.log(
|
||||||
`\n[eposta] (dev) ${to} adresine gönderilecekti — ${icerik.subject}\n${icerik.text}\n`,
|
`\n[eposta] (konsol modu) ${to} adresine gönderilecekti — ${icerik.subject}\n${icerik.text}\n`,
|
||||||
);
|
);
|
||||||
return;
|
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 } = await import("resend");
|
||||||
const resend = new Resend(process.env.RESEND_API_KEY);
|
const resend = new Resend(anahtar);
|
||||||
const { error } = await resend.emails.send({
|
const { error } = await resend.emails.send({
|
||||||
from: process.env.EMAIL_FROM ?? "KolayTercih <onboarding@resend.dev>",
|
from: gonderen || "KolayTercih <onboarding@resend.dev>",
|
||||||
to,
|
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,
|
subject: icerik.subject,
|
||||||
html: icerik.html,
|
html: icerik.html,
|
||||||
// Düz metin alternatifi spam puanını düşürür ve HTML'i engelleyen
|
// Düz metin alternatifi spam puanını düşürür ve HTML'i engelleyen
|
||||||
|
|||||||
Reference in New Issue
Block a user