Çok ajanlı gece vardiyasının çıktıları: analitik (Rybbit) bulguları, SEO/GEO konu planı, marka stratejisi, canlı mail testi, CEO kararları (2 tur), CTO şartnamesi, yazar/yazılımcı raporları, bağımsız kod denetimi ve sabah özeti (yazılı + sesli). Kod değişiklikleri gece/* dallarında. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
104 lines
16 KiB
Markdown
104 lines
16 KiB
Markdown
# 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 <giris@mail.kolaytercih.com>` — `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 <giris@mail.kolaytercih.com>` — 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ı). `<link>` 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 `<head>`'de Google Fonts `<link>` | 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.
|