Next.js üzerinde kendi webmail'inizi barındırmak, SMTP sunucusu işletmek zorunda olduğunuz anlamına gelmez. Buradaki örnekler mimariyi gösterir; güncel kodun tamamı veya doğrudan kurulabilecek paket değildir. İnternetten SMTP kabul etmek, spam ile uğraşmak ve IP itibarını korumak gibi zor tarafları bunu zaten yapan altyapıya bırakıyorum. Cloudflare Email Worker gelen mesajı uygulamama iletir; transactional e-posta sağlayıcısı yanıtlarımı gönderir; dosya deposu mesajları saklar; Next.js App Router arayüzü bunları birleştirir. Uygulama, Cloudflare arkasındaki VPS'te Docker ile çalışır. Bu sitedeki /mail, tam bir kullanıcı için tasarlandı: benim için.
Tek kullanıcılı kapsamın neden bilinçli bir özellik olduğunu, parçaların mimaride nasıl birleştiğini ve kendi postamı işletmenin normalde yönetilen kutunun gizlediği sınırlar hakkında ne öğrettiğini anlatıyorum. Gizli yapılandırma veya kimlik bilgileri yerine mimari sorumluluklara odaklanıyorum.
Temel noktalar
- SMTP sunucusu gerekmez: gelen posta Cloudflare Email Worker'dan, giden posta sağlayıcı API'sinden geçer. İtibarın hassas olduğu altyapıyı bu hizmetler işletir.
- Tek kullanıcı birçok alt sistemi kaldırır: çok kullanıcılı hesap, tenant izolasyonu ve kişi başına kota gerekmez. Model “benim postam”dır.
- Dosya deposu bu ölçekte yeterlidir: diskte mesajlar ve sorgulara uygun metadata/index yönetimi kolay incelenir ve yedeklenir.
- App Router sınırı nettir: sunucu rotaları depoyu ve sağlayıcı kimlik bilgilerini sahiplenir; interaktif MailApp client'ı listeleri ve gövdeleri doğrulanmış handler'lardan alır.
- Self-hosting, e-postanın hangi zorluklarını sahiplenmeniz gerektiğini öğretir. Hepsini kendiniz çözmek zorunda değilsiniz.
Neden tek kullanıcılı mail merkezi?
Sebep maliyet değil, kontrol ve anlayıştır. Yönetilen kutu harcanan saat bakımından daha ucuzdur. Kendi client'ınızı geliştirmek, alan adını, adresi, arşivi ve günlük akışı nasıl istediğinize göre düzenleme imkânı verir.
Kapsamı yönetilebilir kılan karar tek kullanıcıdır. Çok kullanıcılı sistem, kişi başına kimlik doğrulama, tenant izolasyonu, kota ve bunları yöneten admin yüzeyi gerektirir. “Bir kullanıcı” kararı bu alt sistemleri kaldırır. Harici e-posta hesapları bağlanabilse de uygulamanın sahibi yine tek kişidir; bağlı posta hesapları farklı uygulama kullanıcıları değildir. Güvenlik kararı “sahibi mi, değil mi?” etrafında kurulabilir.
Problemi küçültme isteği, Android feature/core/data sınırlarında da aynıydı: yazmak zorunda kalmadığınız kodu bakımda tutmanız gerekmez.
Mimariye genel bakış
Gelen ve giden posta aynı depoyu paylaşan iki akıştır.
Gelen: alan adına gönderilen mesaj Cloudflare e-posta altyapısına ulaşır, Email Worker'a yönlenir. Worker envelope bilgisi ve ham mesajı Next.js HTTP ucuna aktarır; uygulama ayrıştırıp depoya yazar.
Giden: arayüzden gönderilen mesaj uygulama tarafından hazırlanır ve transactional sağlayıcının API'sine iletilir. Gerçek SMTP teslimini ve gönderici itibarını sağlayıcı üstlenir.
Depo, UI ve endpoint'ler tek Next.js uygulamasındadır. Önünde Cloudflare, sunucuda Docker vardır. Bu temel tasarım için ayrı database hizmeti, mail daemon veya mesaj kuyruğu gerekmez. Küçük bir yüzey işletmek tasarımın amacıdır.
Gelen posta: girişte Cloudflare Email Worker
Cloudflare Email Routing, alan adının postasını bir Worker'a yönlendirebilir. Worker'ın email() handler'ı, envelope from/to alanları ve MIME içeriğinin ReadableStream biçimindeki raw alanını alır. Akış bir kez okunup buffer'lanarak uygulamaya aktarılabilir:
export default {
async email(message, env, ctx) {
// raw is a single-use ReadableStream of the full MIME message.
const mime = await new Response(message.raw).arrayBuffer();
await fetch(env.INBOUND_ENDPOINT, {
method: "POST",
headers: {
"content-type": "message/rfc822",
"x-envelope-from": message.from,
"x-envelope-to": message.to,
// The Worker authenticates itself to the endpoint here.
// How it proves it is the real Worker stays out of this post.
},
body: mime,
});
},
};
raw tek kullanımlık akıştır; byte'lara birden fazla işlem yapılacaksa önce buffer alın. İkinci sınır endpoint'in internete açık olmasıdır: uygulama isteği gerçekten yetkili Worker'ın gönderdiğini doğrulamalıdır. Kimlik doğrulamayı tasarlayıp sırları sunucu yapılandırmasında tutmak gerekir; URL'yi bilmek yetki değildir.
Uygulama tarafı App Router route handler'dır. Web Request/Response API'leriyle ham MIME için request.json() yerine request.text() veya byte okuma kullanılır. Şematik örnek:
// app/api/inbound/route.ts
export async function POST(request: Request) {
// Verify the caller is the Worker before doing anything else.
await assertTrustedSender(request);
const raw = await request.text();
const parsed = await parseMime(raw); // headers, parts, attachments
await store.save(parsed);
return new Response(null, { status: 204 });
}
assertTrustedSender, parseMime ve store.save burada mimari sorumlulukları temsil eder. Gerçek handler'ın payload sözleşmesi kendi Worker'ıyla eşleşmelidir. MIME iç içe multipart, farklı karakter setleri ve attachment kodlamaları içerir. Bu ayrıntıda kendi parser'ımı yazmak yerine iyi test edilmiş parser kullanırım.
Giden posta: transactional sağlayıcı
Bir mesajın spam yerine alıcının kutusuna ulaşması SPF, DKIM, DMARC uyumu, gönderen IP itibarı ve önceki davranışına bağlıdır. Yeni VPS IP'siyle doğrudan teslim işletmek, kod yazmanın ötesinde süreklilik gerektirir.
Bu yüzden mesajı uygulamada hazırlayıp sağlayıcının API'sine veriyorum. Sağlayıcı teslimi kendi altyapısıyla yapar ve alan adı için DKIM imzalar. Uygulama açısından bu doğrulanmış HTTPS çağrısıdır; itibarı ayrıca oluşturup savunmak yerine hizmet olarak alırım. Gelen akışta olduğu gibi internete açık, kötüye kullanıma maruz kalan kısmı o konuda uzman altyapı sahiplenir.
Mesaj deposu: diskte dosyalar
Bir kişinin yıllarca postası bile çoğunlukla attachment'ların kapladığı birkaç gigabyte düzeyindedir. Bu ölçekte dosya deposu makul seçimdir. Mesajlar dosyalarda tutulur; store, mesaj metadata'sını tarar ve sorguların gerektirdiği index/cache'leri yönetir. Ayrı database sunucusu işletilmez.
Operasyonel kazanç yedeklemedir. Veri dosyalardan oluştuğunda filesystem snapshot ve şifreli, nesiller halinde yedek rotasyonu mevcut araçlarla kurulabilir.
Sınırlar açıktır: tam metin arama kendi index'ine ihtiyaç duyabilir, eşzamanlı yazıcılar koordinasyon gerektirir ve aynı tasarımı binlerce posta kutusuna çevirmek doğru olmaz. Tek kullanıcılı kapsam bu sınırları yönetilebilir tutar.
UI: Next.js App Router
Güncel MailApp interaktif client component'tir. Yazma, seçim, klavye gezinmesi, liste ve mesaj okuma tarayıcıda çalışır. Oturumla korunan App Router handler'ları depo işlemlerini ve sağlayıcı çağrılarını yapar. Sunucuda render edilen route shell'leri giriş ve navigasyonu kurar. Gelen, giden ve taşıma temel işlemlerdir; kişiler, takvim ve harici hesaplar ilave handler'lar getirir.
Tarayıcı ihtiyaç duyduğu liste veya gövdeyi ister. Liste özetleri tembel yüklenen mesaj gövdelerinden ayrıdır. Güvenilmeyen mesaj HTML'i sanitize edilip sınırlandırılmış frame içinde gösterilir; istekler sırasında UI yükleme durumu gösterebilir. Sağlayıcı sırları ve filesystem erişimi client bundle'a gitmez.
İşletim: Cloudflare arkasındaki VPS'te Docker
Dağıtımın sade kalmasını istiyorum. Uygulama container'da, kalıcı posta verisi mount edilen volume'da, yapılandırma sunucu ortamında tutulur. Cloudflare öndeki proxy/TLS katmanıdır. Aşağıdaki compose örneği bu ayrımı gösterir; tek başına proxy kurulumu değildir:
services:
webmail:
image: webmail:latest
restart: unless-stopped
volumes:
- ./maildata:/data/mail # the file-based store, persisted
env_file: .env
ports:
- "127.0.0.1:3000:3000" # only Cloudflare reaches this, via the proxy
Portu 127.0.0.1'e bağlamak container portunu doğrudan internete açmaz; trafiği yerel origin proxy üzerinden geçirmek gerekir. Mail deposu mounted volume olduğundan image'ın yeniden dağıtılması veriyi silmez. SecureCheck yaklaşımındaki gibi güven sınırını seçip geçişleri açık tanımlamak önemlidir.
Tek kullanıcı için tasarlamanın bedelleri
Kazanç; tenant sistemi, kişi başına kota ve çok kullanıcı admin yönetiminin kalkmasıdır. Bedel paylaşım, delegasyon veya “ekibe birini ekleme” gibi beklentilerin kapsam dışında olmasıdır. Ölçekleme de bir mailbox için daha büyük makineye geçmekle sınırlı tutulabilir.
Tek sahip olmak kurtarma sorumluluğunu da tek kişiye bırakır. Erişimi kaybeder veya depoyu silerseniz geri getirecek ikinci admin yoktur. Bu yüzden yedek ve bağımsız kurtarma provası, görünür UI özellikleri kadar önemlidir.
Self-hosting bana ne öğretti?
E-postanın önemli zorluğu teknik olduğu kadar sosyal ve kötüye kullanımla ilgilidir. MIME ayrıştırmak karmaşıktır ama sınırlı problemdir. Başkalarının kutusuna güvenilir teslim yapmak, uzun süreli itibar yönetimidir. Benim için doğru mühendislik tercihi, teslim ve gelen yönlendirmeyi bu altyapıya bırakıp gerçekten sahiplenmek istediğim client ve arşivi geliştirmek oldu.
İkinci ders kapsamın gücüdür. “Tek kullanıcı” kararı birçok soyutlamanın ekleyebileceğinden daha fazla kod ve riski kaldırdı. Aspectify'da ortak sorumlulukları iş mantığından çıkarmak da benzer bir düşünceye dayanır: bakımını yapacağınız şeyi küçültmek, onu yönetmeyi kolaylaştırır. Mail client'ımı işletmek, hangi sorumlulukların bana ait olduğunu bu kadar açık görmemi sağladı.