# 04 — Mail QA testi (canlı, tempmail) — 2026-09-21 Ajan: Mail QA. Ortam: canlı `https://kolaytercih.com`, kullanıcının Chrome'u (Claude in Chrome) + Bash (`curl`, `dig`). Kod değiştirilmedi; yalnızca bu dosya yazıldı. ## Özet (5 madde) 1. **Sonuç: mail sistemi canlıda ÇALIŞIYOR.** Magic link isteği `HTTP 200 {"status":true}` döndü, mail geçici adrese **~10 saniyede** ulaştı (2 istekte 2 mail, 11 sn ve 10 sn). 2. **Gönderen doğru:** `KolayTercih ` — `onboarding@resend.dev` fallback'i canlıda KULLANILMIYOR (`EMAIL_FROM` tanımlı). DKIM `d=mail.kolaytercih.com; s=resend` imzalı, SPF (`send.mail.kolaytercih.com`) DNS'te mevcut. 3. **İçerik temiz:** konu ve gövdede Türkçe karakterler doğru (UTF-8, quoted-printable), `multipart/alternative` (düz metin + HTML) var, link host'u `https://kolaytercih.com/giris/dogrula?token=…&callbackURL=%2F&errorCallbackURL=…` (localhost/yanlış domain yok), "15 dakika / tek kullanımlık" bilgisi var, gelen kutusu önizleme metni çalışıyor. 4. **En önemli eksik: DMARC kaydı YOK** (`_dmarc.kolaytercih.com` ve `_dmarc.mail.kolaytercih.com` boş). Ayrıca apex'te SPF yok (MX Google'a gidiyor), `mail.kolaytercih.com`'un MX'i ve mailde `Reply-To` yok → kullanıcı maili yanıtlarsa bounce olur. 5. **Test kısıtları:** (a) Chrome'da site sahibinin oturumu açık olduğu için `/giris` formu ana sayfaya yönlendi; oturuma dokunmamak için formun attığı isteğin aynısı çerezsiz `curl` ile gönderildi → **form UI'ı (başarı kutusu, toast, konsol) test EDİLEMEDİ**. (b) Magic link'e kural gereği **tıklanmadı** → giriş/hesap oluşturma/deneme kredisi adımı **elle doğrulanmalı**. Toplam 2 istek atıldı (sınır 3). ## Test günlüğü (saatler TSİ, +03; mail başlıkları UTC) | Saat | Adım | Gözlem | |---|---|---| | 01:38 | Kod okuma + ilk DNS sorguları | Bkz. envanter. (Not: ilk `dig` döngüm zsh kelime-bölmeme yüzünden geçersizdi; 01:47'de doğru biçimde tekrarlandı — aşağıdaki DNS tablosu doğru olandır.) | | 01:38 | Yeni sekme → `guerrillamail.com` | CAPTCHA/çerez bandı çıkmadı. Üretilen kutu: `uatxlibg@sharklasers.com` (karışık takma ad: `yu18q9+a33wualkwzf8o@sharklasers.com`). Hesap açılmadı. | | 01:39 | Yeni sekme → `https://kolaytercih.com/giris` | Sayfa `/`'ye yönlendi: header'da "55 · Sayısal", "Kendi Listem", kredi 0, çıkış ikonu → **site sahibinin oturumu açık** (`giris-karti.tsx:23` `if (session) redirect`). Çıkış yapmak kullanıcı oturumuna dokunmak olacağı için yapılmadı. Çerezsiz `curl https://kolaytercih.com/giris` → 200 (sayfa ayakta). | | 01:40:12 | **İstek 1** — `POST /api/auth/sign-in/magic-link` (çerezsiz curl, `Origin: https://kolaytercih.com`, gövde formdakiyle aynı: email + `callbackURL:"/"` + `errorCallbackURL`) | `HTTP/2 200`, `{"status":true}`, 0,68 sn. | | 01:40:23 (22:40:23Z) | Mail 1 geldi | Resend/SES `Date: 22:40:13Z`, tempmail `Received: 22:40:15Z`, kutuda 22:40:23Z → **gecikme ≈ 3–11 sn**. | | 01:40:34 | **İstek 2** — aynı adres, 22 sn sonra (rate-limit gözlemi) | `HTTP/2 200`, `{"status":true}`, 0,51 sn. Engelleme/uyarı yok, rate-limit başlığı yok. | | 01:40:44 (22:40:44Z) | Mail 2 geldi | Gecikme ≈ 10 sn. **Token farklı** (`znpz…ELUk` → `ISaK…VLr`); ilk token'ın iptal edilip edilmediği tıklanmadan doğrulanamaz (better-auth 1.6.23 her istekte yeni verification satırı açar; eskisi süresi dolana dek geçerli kalır — koddan çıkarım). | | 01:42 | Mail 1 incelendi (HTML görünüm + "Show Original" ham kaynak) | Aşağıda. | | 01:50 | 3 sekme de kapatıldı | Sekme grubu boş. Magic link'e tıklanmadı, URL yalnızca metin olarak okundu. | Konsol hatası / ağ sekmesi: form tarayıcıda açılamadığı için **yok**; ağ kanıtı curl çıktısıdır. ### Gelen mailin incelemesi (ham kaynak) | Kriter | Bulgu | |---|---| | From | `KolayTercih ` — kendi alan adı, resend.dev değil | | Konu | `Merhaba, giriş bağlantını gönderdik` (RFC 2047 UTF-8 Q-encoding, doğru çözülüyor). Guerrillamail açık-mail başlığında `gönderdik` gösterdi — bu tempmail arayüzünün kusuru; liste görünümünde ve ham başlıkta doğru. | | DKIM | 2 imza: `d=mail.kolaytercih.com; s=resend` + `d=amazonses.com`. DNS'te `resend._domainkey.mail.kolaytercih.com` anahtarı mevcut. | | SPF | `send.mail.kolaytercih.com TXT "v=spf1 include:amazonses.com ~all"` + `MX feedback-smtp.us-east-1.amazonses.com` mevcut. Gönderen IP 54.240.9.44 (Amazon SES, us-east-1). | | DMARC | **YOK** (apex ve `mail.` alt alanında). | | Gövde | `multipart/alternative`: `text/plain; utf-8` + `text/html; utf-8`, ikisi de quoted-printable. Türkçe karakterler (ş, ğ, ı, ö, ç, ü) iki parçada da doğru. | | HTML | Tablo tabanlı, satır içi stil; başlık, "15 dakika" vurgusu, mavi "Giriş yap →" butonu, yedek düz link, "sen yapmadıysan" notu, footer göründü. Logo `https://kolaytercih.com/kolay-tercih-mark.png` → curl ile 200 `image/png` 5129 B (tempmail görselleri varsayılan engelliyor; "Display Images"e basınca reklam katmanı çıktı, görsel render'ı gözle doğrulanamadı). `` ile Google Fonts yükleniyor — çoğu istemci yok sayar, zararsız. | | Link | Host `kolaytercih.com`, yol `/giris/dogrula` (token'ı tüketmeyen onay sayfası — SafeLinks koruması tasarlandığı gibi). Parametreler: `token` (32 karakter), `callbackURL=%2F`, `errorCallbackURL=%2Fgiris%3Fhata%3Dgecersiz-baglanti%26callback%3D%252F`. HTML'de `&` kaçışı doğru. | | Geçerlilik | "15 dakika geçerli ve yalnızca bir kez kullanılabilir" (HTML + düz metin + önizleme). `auth.ts:78` `expiresIn: 900` ile tutarlı. | | Footer | "KolayTercih · YKS tercih listeni dakikalar içinde hazırla / Bu e-postayı hesabınla ilgili bir işlem yaptığın için aldın." **İletişim adresi, site linki, destek e-postası yok.** | | Reply-To | Yok. `mail.kolaytercih.com`'un MX'i olmadığından yanıtlar bounce olur. | | Spam işareti | Tempmail'de uyarı yok (Guerrillamail spam skoru göstermez). Gmail/Outlook gelen-kutusu/spam yerleşimi **test edilmedi**. | ### DNS (01:47, `dig @1.1.1.1`) | Kayıt | Sonuç | |---|---| | `TXT kolaytercih.com` | yalnızca 2× `google-site-verification` — **SPF yok** | | `MX kolaytercih.com` | `1 smtp.google.com.` (Google Workspace; `destek@kolaytercih.com` buraya düşer) | | `TXT _dmarc.kolaytercih.com` | **boş** | | `TXT _dmarc.mail.kolaytercih.com` | **boş** | | `TXT resend._domainkey.kolaytercih.com` | boş (beklenen; Resend alan adı `mail.` alt alanı) | | `TXT resend._domainkey.mail.kolaytercih.com` | DKIM anahtarı mevcut | | `TXT send.mail.kolaytercih.com` | `v=spf1 include:amazonses.com ~all` | | `MX send.mail.kolaytercih.com` | `10 feedback-smtp.us-east-1.amazonses.com.` | | `MX/TXT mail.kolaytercih.com` | boş | ## E-posta envanteri Sistemde `epostaGonder` çağıran yalnızca 2 yer var (`grep -rn epostaGonder src`). Ödeme makbuzu, hoş geldin, rapor-hazır vb. e-posta **yok**. | Şablon | Tetikleyici | Dosya | Test edildi mi | |---|---|---|---| | `magicLinkEpostasi(url)` — "Merhaba, giriş bağlantını gönderdik" | `/giris` formu → `authClient.signIn.magicLink` → `POST /api/auth/sign-in/magic-link` → better-auth `sendMagicLink` → `sendMagicLinkEmail` | `src/lib/eposta.ts:122`, `src/lib/auth.ts:25-41,74-82`, `src/features/kullanici/components/giris-form.tsx:67-90` | **EVET** — uçtan uca teslim + içerik. Form UI'ı ve linke tıklama HARİÇ. | | `krediHatirlatmaEpostasi()` — "Listen seni bekliyor — açık satırların hazır" | Saatlik zamanlayıcı (`instrumentation.ts:11-12` → `krediHatirlatmaBaslat`): `krediBittiAt ≤ now-48s` + hatırlatma gönderilmemiş + bakiye < `RAPOR_KREDI` olan kullanıcılara tek seferlik | `src/lib/eposta.ts:167`, `src/lib/kredi-hatirlatma.ts:27-59` | **HAYIR** — 48 saat + kredisi bitmiş hesap gerektirir; canlıda tetiklenemez. Aynı `epostaGonder` yolunu kullandığı için gönderim altyapısı dolaylı doğrulandı. | | (Google OAuth) | E-posta göndermez | `src/lib/auth.ts:67-72` | — | ## Yazılımcı ajanlara iletilecek eksikler | # | Eksik | Kanıt | Önem | Önerilen düzeltme | Dosya:satır | |---|---|---|---|---|---| | 1 | DMARC kaydı yok | `dig TXT _dmarc.kolaytercih.com` ve `_dmarc.mail.…` boş | **orta** (Gmail/Yahoo gönderici kuralları DMARC bekliyor; spoofing'e açık; teslim oranı riski) | DNS (kod değil): `_dmarc.kolaytercih.com TXT "v=DMARC1; p=none; rua=mailto:destek@kolaytercih.com"` ile başla, raporlar temizse `p=quarantine`. Alt alanı da kapsar. **Kullanıcı işi (Cloudflare).** | — | | 2 | Apex'te SPF yok (MX Google'da) | `dig TXT kolaytercih.com` yalnızca google-site-verification | düşük-orta (`destek@`'ten giden yanıtlar SPF'siz) | DNS: `kolaytercih.com TXT "v=spf1 include:_spf.google.com ~all"`. **Kullanıcı işi.** | — | | 3 | `Reply-To` yok + `mail.kolaytercih.com` MX'siz → maile yanıt bounce olur | Ham başlıklarda Reply-To yok; `dig MX mail.kolaytercih.com` boş | düşük | `resend.emails.send`'e `replyTo: "destek@kolaytercih.com"` ekle (env ile: `EMAIL_REPLY_TO`). | `src/lib/eposta-gonder.ts:21-29` | | 4 | `EMAIL_FROM` yoksa sessizce `onboarding@resend.dev`'e düşüyor | Kod. Canlıda şu an doğru ayarlı (From = `giris@mail.kolaytercih.com`), ama env kaybolursa Resend yalnızca hesap sahibine gönderir → tüm kullanıcı girişleri 5xx olur | orta (gizli konfig tuzağı) | Production'da `EMAIL_FROM` yoksa açılışta/ilk gönderimde net hata fırlat veya `console.error` ile uyar; fallback'i yalnızca dev'de bırak. | `src/lib/eposta-gonder.ts:22` | | 5 | Production'da `RESEND_API_KEY` yoksa magic link **konsola yazılıp başarı dönüyor** | Kod: `auth.ts:33-37` ve `eposta-gonder.ts:13-18` `NODE_ENV` kontrolü yapmıyor. Kullanıcı "gönderildi" görür, mail çıkmaz; ayrıca giriş token'ı prod loglarına düşer | orta (şu an canlıda anahtar var — test kanıtı; risk regresyon) | `NODE_ENV === "production"` iken anahtar yoksa `throw`; token'lı linki prod'da asla loglama. | `src/lib/auth.ts:33-37`, `src/lib/eposta-gonder.ts:13-18` | | 6 | Adres bazlı gönderim sınırı yok; yalnızca better-auth varsayılan IP limiti (magic-link yolu için 5 istek/60 sn) | Test: aynı adrese 22 sn arayla 2 istek → 2 mail, 200/200. `auth.ts`'te `rateLimit`/`advanced.ipAddress` ayarı yok (`node_modules/better-auth/dist/plugins/magic-link/index.mjs:157-163`). UI'daki 30 sn geri sayım yalnızca istemci tarafı (`giris-form.tsx:41-52`) | orta (üçüncü kişiye mail bombardımanı + Resend kotasının yakılması; Traefik arkasında IP başlığının doğru okunduğu **doğrulanmalı** — okunmuyorsa tüm kullanıcılar tek kovayı paylaşır ya da limit hiç işlemez) | `sendMagicLinkEmail` içinde e-posta başına sunucu tarafı soğuma (ör. 60 sn'de 1, saatte 5) — `verification` tablosundaki son kayıt zamanından türetilebilir; `advanced.ipAddress.ipAddressHeaders`'ı Traefik'e göre sabitle. | `src/lib/auth.ts:25-41,74-82` | | 7 | Yeni istek eski token'ı iptal etmiyor | 2 mailde 2 farklı token; better-auth her istekte yeni satır açar (koddan çıkarım, tıklanarak doğrulanmadı) | düşük | Kabul edilebilir (15 dk TTL). İstenirse gönderimden önce aynı e-postanın eski verification satırları silinebilir. | `src/lib/auth.ts:79-81` | | 8 | Form hata mesajı tek tip: 429 (rate limit), 5xx (Resend hatası) ve ağ hatası aynı "Bağlantı gönderilemedi. Tekrar dener misin?" toast'ı | Kod; `error.status` okunmuyor | düşük | `error.status === 429` → "Çok sık denedin, bir dakika sonra tekrar dene"; diğerleri → mevcut mesaj + "Google ile devam et" önerisi. Hata olayını analitiğe yaz (`olay("giris_hata", {status})`) — şu an yalnızca başarı loglanıyor. | `src/features/kullanici/components/giris-form.tsx:83-86,89` | | 9 | Gönderim hatası yalnızca `console.error`; alarm/izleme yok | Kod | düşük-orta (Resend kotası dolarsa kimse fark etmez; girişler sessizce ölür) | Hata sayacını mevcut analitiğe/health ucuna bağla ya da Resend webhook'u (bounced/complained) ekle. | `src/lib/eposta-gonder.ts:33-36`, `src/lib/kredi-hatirlatma.ts:52-57` | | 10 | Footer'da iletişim/site linki yok; kredi hatırlatma mailinde "bu tür e-postaları alma" seçeneği yok | Mail gövdesi + `eposta.ts:96-101` | düşük (magic link) / **orta** (hatırlatma: ticari ileti sayılabilir — İYS/KVKK sınıfı **doğrulanmalı**; kod yorumunda da "KVKK sınıfı netleşene dek" notu var) | Footer'a `kolaytercih.com` + `destek@kolaytercih.com` ekle; hatırlatma şablonuna tek tık vazgeçme linki + `List-Unsubscribe` başlığı. | `src/lib/eposta.ts:96-101,167-205` | | 11 | Kredi hatırlatması at-most-once: Resend hatasında hatırlatma kalıcı olarak yanıyor | Kod yorumu (`kredi-hatirlatma.ts:23-25`) — bilinçli tercih | düşük | İstenirse hata durumunda `krediHatirlatmaGonderildiAt`'i geri `NULL`'a çek (tek yeniden deneme). | `src/lib/kredi-hatirlatma.ts:42-57` | | 12 | Tek kullanımlık (disposable) alan adları kabul ediliyor → her tempmail = yeni deneme kredisi | Test: `sharklasers.com` adresine mail gitti; `auth.ts:84-97` her yeni kullanıcıya `DENEME_KREDISI` veriyor; src'de disposable filtresi yok | orta (deneme kredisi suistimali; ürün kararı — CEO'ya) | Bilinen disposable alan adları listesiyle `sendMagicLink` öncesi ret ya da deneme kredisini yalnızca Google girişi/doğrulanmış kalıcı alan adlarına ver. | `src/lib/auth.ts:79-97` | | 13 | HTML ``'de Google Fonts `` | Ham kaynak | düşük (çoğu istemci yok sayar; bazı spam filtrelerinde harici kaynak = küçük eksi puan) | Kaldırmak güvenli; sistem fontuna zaten düşüyor. | `src/lib/eposta.ts:79-80` | ## Kullanıcının (Bilal) elle yapması gerekenler 1. **Magic link'e tıklama testi (bu oturumda yasaktı):** gizli pencerede `/giris` → kendi ikinci bir adresinle link iste → maildeki "Giriş yap →" → `/giris/dogrula` onay kartı → butona bas → oturum açıldı mı, yeni kullanıcıya deneme kredisi yazıldı mı, `callbackURL` doğru mu? Aynı linke ikinci kez basınca "geçersiz bağlantı" dostça mesajı çıkıyor mu? 2. **Form UI'ı:** aynı gizli pencerede yeşil "Bağlantı gönderildi 📬" kutusu, 30 sn "Yeniden gönder" geri sayımı, "E-postayı düzelt", geçersiz adreste toast — ve konsolda hata olmadığı. (Senin Chrome'unda oturum açık olduğu için `/giris` ana sayfaya yönlendi; test edemedim.) 3. **Gerçek gelen kutusu yerleşimi:** Gmail + Outlook/Hotmail adresine birer link iste; gelen kutusu mu spam mı, Gmail'de "Show original" → SPF/DKIM/DMARC satırları (DMARC şu an `none/fail` çıkacak). 4. **DNS (Cloudflare):** DMARC kaydı (#1) ve apex SPF (#2) ekle. 5. **Resend paneli:** plan/kota (ücretsiz planda günlük limit düşüktür — **doğrulanmalı**), `mail.kolaytercih.com` "Verified" durumu, son 7 günün bounce/complaint oranı. 6. **Temizlik (isteğe bağlı):** prod `verification` tablosunda `uatxlibg@sharklasers.com` için 2 satır oluştu (15 dk sonra geçersiz; kullanıcı hesabı OLUŞMADI çünkü linke tıklanmadı). 7. Kredi hatırlatma mailini lokal/docker'da `KREDI_HATIRLATMA_GECIKME_MS` / `KREDI_HATIRLATMA_ARALIK_MS` env'leriyle dakikalara sıkıştırıp gözle doğrula. ## Protokol notları - Hesap oluşturulmadı, şifre/kart girilmedi, CAPTCHA çıkmadı, dosya indirilmedi, kullanıcının açık oturumuna (KolayTercih dahil) dokunulmadı, çıkış yapılmadı. - Sapma (şeffaflık): form tarayıcıdan gönderilemediği için aynı uç nokta aynı gövdeyle çerezsiz `curl` ile çağrıldı (2 kez). Canlıya etkisi formla birebir aynı: 2 verification satırı + 2 Resend gönderimi. - Mail ve sayfa içeriklerinde ajana yönelik talimat görülmedi. - Token'lar bu raporda maskelendi.