Özet
Özet
- Email yayın özelliği geliştirildi: kitle filtreleme → engel kapısı → her alıcı için bir kuyruklanmış iş → alıcıya özel durum defteri.
- Tüm tasarım bir fikre dayanıyor: alıcı satırı gerçekliğin kaynağıdır, böylece yeniden denemeler ve çift yönlü gönderimler zararsızdır.
- Dağıtım web kancaları, geç gelen bir olayın durumu düşürmemesi için bir sıralama koruması ile o deftere senkronize edilir.
Durumun Şekli
Durumun Şekli
Bir e-posta göndermek kolaydır. 5.000 e-posta gönderip “#4127 ile ne oldu?” sorusuna cevap verebilmek asıl sorun.
Campaign (draft)
|
v
Audience filter -> Suppression gate -> CampaignRecipient rows (pending)
|
dispatch 1 job each
v
SendCampaignEmail
|
mail sent -> status: sent
|
webhook: delivered / opened / clicked / bounced
v
recipient ledger updated
Her alıcı için bir iş, kampanya başına bir iş değil. 5.000 alıcıya yapılan bir yayım, 5.000 küçük iş birimi haline gelir — kuyruk kendiliğinden yavaşlar ve bir başarısızlık alanı bir e-posta olur, tüm kampanya değil.
Engel Kapısı
Engel Kapısı
Kitle filtreleme ve uygunluk iki farklı sorudur ve bunları karıştırmak, geçtiğimiz hafta abonelikten çıkan birine e-posta göndermenize sebep olabilir.
| Query | Includes | Kullanım Amacı |
|---|---|---|
baseQuery() | filtreyle eşleşir | UI’de önizleme sayıları |
eligibleQuery() | filtre + ulaşılabilir | gerçek gönderim için |
public function eligibleQuery(array $audience): Builder
{
return $this->baseQuery($audience)->emailReachable();
}
emailReachable(), iletişim modelinde bir kapsamdır — iletişimsizlik, hala abone, e-posta adresine sahip. Tek bir kapsam, kuralları değiştirmek için tek bir yer, çağrı noktasında unutulamaz.
Idempotency Satırda Yaşıyor
Idempotency Satırda Yaşıyor
İş, modeller değil, kimlikleri alır ve her gönderim, herhangi bir şey yapmadan önce alıcının durumunu yeniden kontrol eder:
class SendCampaignEmail implements ShouldQueue
{
public int $tries = 3;
public int $backoff = 30;
public function __construct(
public readonly int $recipientId,
public readonly int $tenantId,
) {}
// ...
}
Queue, durumu kontrol eden bir koruma ile birlikte yeniden gönderildiğinde, ikinci denemeyi gereksiz hale getirir. Burada basit bir yöntem var — dağıtılmış bir kilit yok, dedupe tablosu yok, sahip olduğunuz bir satır üzerinde yalnızca bir durum makinesi var.
failed() kancası da en az onun kadar önemlidir. Olmazsa, yeniden denemeler tükendiğinde beklemede kalan bir alıcı, kampanyayı gönderiliyor durumunda sonsuza dek bırakır. Başarısız olarak işaretle, istatistikleri yeniden hesapla, kampanyanın bitmesine izin ver.
Durumu Asla Düşürme
Durumu Asla Düşürme
Dağıtım web kancaları sıralı gelmez. delivered olayı, opened durumundan sonra gelirse, durumu geriye almak doğru değildir.
private const RANK = [
'sent' => 1, => 2, => 3, => 4,
];
Monotonik ilerleme, son duraklar (bounced, unsubscribed, failed) ayrı olarak işlenmektedir. Kampanya yaşam döngüsü ve alıcıya özel durum için iki enum terminolojiyi taşır; her biri label(), color() ve description() ile, böylece UI asla bir dizeyi hardcode etmez.
Abonelikten Çıkma Kamu Yolu
Abonelikten Çıkma Kamu Yolu
Tokenize edilmiş abonelikten çıkma bağlantısı, herhangi bir kimlik doğrulama gerektirmez; bu nedenle, her zaman aynı genel onay mesajını gösterir — geçerli token ya da değil. Aksi takdirde, var olan tokenlar hakkında bir bilgi kaynağı oluşturursunuz.
Abonelikten çıkma işlemi, herhangi bir bekleyen alıcı satırlarını unsubscribed olarak geri doldurur; böylece raporlar dürüst kalır ve hiç gerçekleşmeyecek bir gönderim sayılmaz.
it(, function () {
$recipient = pendingRecipient();
$recipient->contact->unsubscribeFromEmail();
expect($recipient->fresh()->status)
->toBe(CampaignRecipientStatus::UNSUBSCRIBED);
});
Öneri
Öneri
Yayın sistemleri karmaşık hale gelir çünkü insanlar gönderimi güvenilir hale getirmeye çalışır. Bunun yerine defteri güvenilir hale getirin, her işi küçük ve tekrar çalıştırılabilir tutun; böylece güvenilirlik otomatik olarak gelir.
Kaynak: Orijinal Makale


