API ve otomasyon

Kodda bir e-posta beklemek: webhook olmadan yoklama

Bir webhook, posta geldiğinde size haber verir. Webhook yoksa sormanız gerekir — ve soran döngü, tam da uçtan uca testlerin dengesizleştiği, ajanların asılı kaldığı ve betiklerin bir hız sınırına çarptığı yerdir. İşte o döngünün doğru yapması gereken şeyler: bir sayı değil bir son tarih, genişleyen bir aralık, hangi mesajın size ait olduğuna karar veren bir kural, ve beklemenin sizin yerinize yapılabileceği tek yer.

  • Orta düzey
  • 29 dk okuma
Etrafında mavi dairesel bir okla dönen gri bir kronometrenin yanında havada duran mavi bir zarf

Push ve pull, ve her birinin bedeli

Kodunuzun bir mesajın düştüğünü öğrenmesinin yalnızca iki yolu vardır. Ya karşı taraf size söyler, ya da siz sorarsınız. Bunun dışındaki her şey — içinde bir waitFor olan bir istemci kütüphanesi, bir posta kutusunu “akıtan” bir SDK, bloke olan bir test yardımcısı — bu ikisinden birinin, mekanizması gizlenmiş halidir, ve hangisini elinizde tuttuğunuzu, onu hata ayıklamak zorunda kalmadan önce bilmeye değer.

Sunulanların neredeyse tamamını dört düzenleme kapsar.

Bir webhook
Hizmet, posta her geldiğinde, sahibi olduğunuz bir adrese bir HTTP isteği yapar. Bu, olabilecek en ucuz beklemedir — yapacak bir şey olana kadar hiçbir şey yapmazsınız — ve bedeli, herkese açık internette bir adres, postanın geldiği anda çalışır durumda bir dinleyici, isteğin onlardan geldiğini kanıtlayan paylaşılan bir sır, ve dinleyiciniz orada değilken ne olacağına dair kendi yanıtınızdır.
Uzun bir yoklama
İsteği siz yaparsınız, ve sunucu ya posta gelene ya da bir zaman aşımı dolana kadar onu açık tutar. Sizden dışa giden bir bağlantı dışında hiçbir şey istemez, ve sunucuya bekleyen başına bir worker'a mal olur — bunu sunan her hizmetin, hem beklemenin uzunluğuna hem de eşzamanlı bekleyen sayısına bir tavan koymasının nedeni de budur.
Sıradan bir yoklama
Tekrar tekrar sorarsınız, ve her istek, o anda orada ne varsa onunla hemen yanıtlanır. Bir yönlendiricinin arkasındaki bir dizüstü bilgisayardan, gelen yönü olmayan bir CI runner'ından, ve başkasının sandbox'ı içinde çalışan bir ajandan işe yarayan tek düzenlemedir — ve bu rehberin konusunun tamamı da budur.
Bir posta kutusu protokolü
IMAP'ın IDLE'ı vardır, ki bu farklı bir kılığa girmiş uzun bir yoklamadır: bağlantı açık kalır, ve sunucu yeni postayı bunun üzerinden duyurur. Gerçekten push benzeridir, ve kimlik bilgileri olan bir posta kutusu, bir socket'i açık tutabilen ve düştüğünde yeniden bağlanabilen bir istemci, ve komuta uyan bir sunucu ister — ki bu, tek bir mesaja ihtiyaç duyan bir iş için epey bir mekanizmadır.
Posta hizmetiisteği yaparDinleyicinizgenel URL, sır, ayaktaadresinizi çağırırKodunuzisteği yaparPosta kutusuorada olanla yanıtlarsaniyede bir sorarBir webhook, herkese açık internette bir adres ister. Bir yoklama ise bir döngüden başka hiçbir şey istemez.
İki şekil, ve aralarında asıl kararı veren şey. Biri herkese açık internette bir adres ister; diğeri dışa giden bir istek yapabilme yeteneğinden başka hiçbir şey istemez, ki bu ikisinden bir test runner'ının her zaman sahip olduğu tek olanıdır.

Yan yana koyduğunuzda, seçimin zerafetten çok, her birinin kodunuzun çalıştığı makineden ne istediğiyle ilgili olduğu ortaya çıkar.

Sizden ne istediğiWebhookYoklama
Kodunuza ulaşılabilecek bir adresEvet: sertifikalı, internetten yönlendirilebilir herkese açık bir URL.Hayır. Tek gereken, dışa giden bir istektir.
Saklanacak ve döndürülecek bir sırEvet: bir imzalama anahtarı, yoksa bir yabancı size sahte bir mesaj gönderebilir.Hayır. Doğrulanacak hiçbir şey yoktur, çünkü sorulmadan hiçbir şey gelmez.
Postanın düştüğü anda çalışan bir şeyEvet — ve o çökükken, mesajı alıp almayacağınız sizin kararınız değil, gönderenin yeniden deneme politikasıdır.Hayır. Siz bakmazken hiçbir şey kaçırılmaz: posta kutusu, ne olursa olsun onu 5 gün boyunca tutar.
Posta yokken yapılan isteklerHiç yok. Bütün çekiciliği de bu.Aralık başına bir istek — gerçek bedel, ve bu rehberin gerisinin konusu da bu.

Herkesin önce yazdığı döngü

Dört satırdır, yazıldığı gün çalışır, ve her bir sorunu daha sonra ve başka bir yerde ortaya çıkar: sabahın üçünde bir pipeline'da, on bir dakikadır “düşünmekte” olan bir ajanda, döngünüz bütçeyi tuttuğu için bir meslektaşa 429 yanıtı veren bir posta kutusunda.

başlanacak döngü
import time
import requests

while True:
    r = requests.get("https://grabmail.io/api/v1/mailbox",
                     params={"address": "signup-42@grabmail.io"})
    if r.json()["messages"]:
        break
    time.sleep(1)

Onda beş şey yanlıştır, ve yalnızca ilki apaçıktır.

Asla bırakmaz
Bir son tarih yoktur, bu yüzden mesaj gerçekten gelmeyecekken — form adresi reddetti, gönderenin kuyruğu tıkandı, biri alan adını yanlış yazdı — bu döngü başarısız olmaz. Asılı kalır. Asılı kalan bir iş, başarısız olandan daha kötüdür, çünkü günlük, nedenini hiç söylemeden biter.
Denemeleri sayar ve onlara saniye der
Geçiş sayısına bir sınır koysanız bile, “bir saniyede” otuz deneme asla otuz saniye değildir: her geçiş ayrıca bir isteğe mal olur, ve 400 ms süren bir istek otuz saniyenizi kırk ikiye çevirir. Bir yeniden deneme ekleyin, ve aritmetik aritmetik olmaktan çıkar.
Her runner aynı anda sorar
Aynı pipeline'dan yirmi iş başlatın, ve birbirleriyle adım adım aynı yoklamayı yaparlar, çünkü hepsi birbirinden birkaç milisaniye içinde başlamıştır ve hepsi aynı tam saniyeyi uyur. Zirve, ortalamanın yirmi katıdır, ve reddedilen de zirvedir.
En yeni mesajı alır, sizinkini değil
Listedeki ilk kayıt, o posta kutusunun en üstünde ne varsa odur, ki bu herkese açık bir adreste başkasının postası, yeniden kullanılan bir adreste ise geçen haftanın postası olabilir. Gördüğü ilk mesajda çıkan bir döngü, beklediği mesaj gelmeden önce, hiç çekinmeden çıkar.
Her yanıtı bir başarı sayar
Mesaj listesini bir 429'un ya da bir 404'ün içinden okumaya çalışmak, onu açıklayan her şeyden üç çerçeve uzakta bir hata fırlatır, ve onu bir 500'ün içinden okumak hiçbir şey fırlatmayabilir bile. Durum kodu, bakılacak son şey değil ilk şeydir.

Bir saate göre durun, bir sayaca göre değil

Son tarihi bir kez, ilk istekten önce, monotonik bir saatten alın — makine kendi saatini düzelttiğinde geriye sıçramayan bir saatten — ve her geçişin başında ona karşı karşılaştırın. Döngüdeki her şey, o zaman, beklemenin ne kadar sürdüğünü değiştirmeden değişmekte özgürdür: aralığı genişletebilir, bir reddi yeniden deneyebilir ya da ikinci bir filtre ekleyebilirsiniz, ve doksan saniye yine de doksan saniyedir.

Ne kadarının yeterince uzun olduğu, sizinle değil gönderenle ilgili bir sorudur. Bir makinenin bir forma yanıt olarak ürettiği posta genellikle tek haneli saniyelerde teslim edilir; birikmiş bir kuyruk, gri listeleme yapan bir alıcı, ya da saatlik bir toplu iş, tamamen başka bir büyüklük mertebesidir, ve seçtiğiniz hiçbir aralık onu daha erken getirmez.

Ne beklediğinizDürüst bir son tarihGeçtiğinde ne yapılmalı
Bir testin içinde, bir kayıt ya da doğrulama e-postası60 ile 120 saniyeTesti başarısız sayın ve adresi yazdırın. On seferin dokuzunda posta kutusu boştur, çünkü form adresi reddetmiştir, ve adres, günlüğü okuyan herkesin görmesi gereken ilk şeydir.
Bir kişinin az önce istediği bir parola sıfırlama30 ile 60 saniyeOna ulaşmadığını söyleyin ve yeniden göndermeyi önerin. Sessiz bir ekranın ardında dönmeye devam etmeyin: kişi zaten bir ikincisini isteyecektir, ve şimdi iki kod olur.
Bir kaydı kendi başına tamamlayan bir ajanİki ya da üç sunucu tarafı bekleme, yani 50 ile 75 saniyeBunu yanıtta söyleyin. “Bir dakika sonra hâlâ onay e-postası yok” ajanın üzerine hareket edebileceği bir sonuçtur; asla dönmeyen bir araç çağrısı değildir.
Bir bülten, bir fiş, toplu işlenen her şeyDakikalar — ya da hiç beklemeyinBunun yerine bir zamanlamayla yoklayın ve sürecin bitmesine izin verin. On dakika boyunca bir socket üzerinde oturan bir şey, bir proxy, bir runner ya da bir konteyner sınırı tarafından öldürülecek bir şeydir.

Son tarih, hata mesajınız için de dürüst bir yerdir. “signup-42@grabmail.io adresine 90 sn içinde ‘E-postanızı onaylayın’ ile eşleşen hiçbir şey gelmedi” ifadesi; adresi, filtreyi ve bütçeyi adlandırır — ki bu, neler olduğunu çözmek için gereken dört şeyden üçüdür. Dördüncüsü, gerçekten gelen şey, o da yazdırılmaya değer: döngünün görüp reddettiği konuların bir listesi, “test dengesiz” sonucunu bir okumada “konu satırı değişti”ye çevirir.

Ne kadar sık sorulmalı, ve ne zaman genişletilmeli

Taban, hizmetin izin verdiği her neyse odur, ve burada bu, adres başına saniyede bir istektir. Bu caydırıcı bir şey değildir — saniyede bir yoklamak amaçlanan örüntüdür, yönetilecek bir günlük kota, bir aylık kota ve bir burst kredisi yoktur — ama bir tabandır, ve aynı saniyenin içinde iki kez soran bir döngü, ikincisi için daha hızlı bir yanıt değil bir 429 alır.

Sabit bir aralık
Her seferinde bir saniye, son tarihe kadar. On saniyede bitecek bir bekleme için gayet iyidir, ve tek bir runner'daki tek bir test için doğru varsayılandır. Tek kusuru, postanın gelmeyeceği apaçık hale geldikten çok sonra da aynı hızda sormaya devam etmesidir.
Genişleyen bir aralık
Mesaj muhtemelen hâlâ yoldayken bir saniye, sonra bir tavanla iki katına çıkarak — iki, dört, sekiz. Geç gelen bir mesajda biraz gecikmeye mal olur, ve zaten başarısız olacak bir beklemede isteklerin çoğunu kurtarır. Ona bir tavan koyun: tavansız iki katına çıkan bir aralık, iki dakikalık bir son tarihin arka yarısını uyuyarak geçirir.
Jitter: eklenir, hiç çıkarılmaz
Geçişleri rastgele bir kesirle birbirinden ayırın, ki yirmi runner aynı anda sormayı kessin. Alışılmış tarif — sıfır ile aralık arasında rastgele bir değer — burada yanlıştır, çünkü aralığının yarısı bir saniyelik tabanın altına düşer. Rastgeleliği bunun yerine üzerine ekleyin: aralık bir asgaridir, ve jitter bir geçişi yalnızca daha geciktirir, hiç öne almaz.
Seçimi size ait olmayan bir duraklama
Yanıt bir 429 olduğunda, aralık Retry-After'ın söylediği her neyse odur, ve reddedilen geçiş bir deneme değildi. Onu bir deneme sayın, ve hız sınırına çarpan bir döngü, posta kutusunu hiç okumadan tüm son tarihini reddetmeler toplayarak geçirir.

Bir saniyede on beş saniye, sonra sekizlik bir tavana kadar iki katına çıkarak, üzerine jitter eklenmiş halde, bu rehberdeki neredeyse her beklemeyi altı satıra sığdırır:

aralığın kendisi, tek başına
def delay(attempt: int) -> float:
    # One second while the message is probably still in flight, then
    # wider. Never below a second: the list endpoint allows one call
    # per second, per address, so jitter is added and never taken off.
    step = 1.0 if attempt < 15 else min(8.0, 2.0 ** (attempt - 14))
    return step + random.uniform(0.0, step / 2)

Üs, genişlemenin ilk geçişten değil, sabit bölümden sonra başlaması için kaydırılmıştır. Bu kaydırma olmadan, yavaş bir kayıt e-postası düştüğünde aralık çoktan sekiz saniyeye ulaşmış olur, ve on iki saniye sürmesi gereken bir bekleme yirmi sürer.

Bunların hiçbiri ilk istek için geçerli değildir. Herhangi bir uyumadan önce hemen sorun: döngü başladığında posta kutusunda çoktan bulunan bir mesaj — bekleme başlamadan önce tetiklenen her şey için normal durum — fark edilmesi için bir saniyelik gecikmeye mal olmamalıdır.

Bir reddi okumak

Liste uç noktasından gelen her yanıt JSON'dur, ve bir posta kutusu olmayanlar bir şekli paylaşır: dallanılacak şey olan, kararlı bir error etiketi, ve düz metin olan, her an yeniden kelimelendirilebilecek bir message. Çok hızlı gitmenin reddi bir başlık da taşır:

bir reddin göründüğü şekil
HTTP/1.1 429 Too Many Requests
Retry-After: 1
Content-Type: application/json; charset=utf-8

{"error":"rate_limited","message":"one request per second, per address"}

Retry-After tam saniye cinsindendir, ve gerçek rakamdır — belgelerdeki bir sabitten değil, bu adresin bütçesinde gerçekte ne kadar kaldığından alınır. Tam olarak onun kadar uyumak, hem en kibar hem en hızlı seçenektir: daha kısa bir uyku yeniden reddedilir, daha uzun biri ise boşa harcanan zamandır. İşte bir yoklama döngüsünün karşılaşabileceği her şey, ve her yanıtın döngüden gerçekte ne istediği.

Ne dönerNe anlama gelirDöngünün ne yapması gerekir
count: 0 ile bir 200Posta kutusu vardır ve boştur. Bir beklemenin çoğu için normal yanıt budur.Beklemeye devam edin. Bir hata değildir, ve hiçbir zaman olmaz.
429 hatası — rate_limitedÇok hızlı: bu adres için bir saniye içinde ikinci bir liste isteği, ya da bu kaynaktan bir dakikada 1,200'den fazla istek.Retry-After saniye kadar uyuyun, sonra tekrar sorun. Reddi bir deneme saymayın.
404 hatası — unknown_domain@'dan sonraki kısım burada barındırılmıyor. Hemen her zaman bir yazım hatası, ya da MX kaydı hiç buraya yönlendirilmemiş bir alan adı.Durun. Hiçbir bekleme bir alan adını düzeltmez. Size verilen adresi yazdırın.
400 hatası — invalid_addressaddress parametresi eksiktir, 320 karakterden uzundur, ya da ad@alanadı biçiminde değildir.Durun. Bu, çağıranda bir kusurdur, ve her geçişte aynı kusur olacaktır.
400 hatası — bad_cursorbefore değeri hiç bir mesaj id'si gibi biçimlenmemiştir. Biçimi doğru olan ama süresi dolmuş bir id bu hata değildir: boş bir sayfayla 200 yanıtı verir.Sayfalamayı durdurun ve ilk sayfadan yeniden başlayın.
404not_found, tek bir mesajdanO id o posta kutusunda değildir — ya da vardı, ve o zamandan beri süresi dolmuş ya da silinmiştir.Onu gecikmiş değil gitmiş sayın. Birkaç saniye önce bir listelemede gördüğünüz bir id geri gelmez.
500 hatası — storage_failedPosta kutusu okunurken bizim tarafımızda bir şey başarısız oldu.Tekrar sorun, ama son tarihin yönetmesine izin verin ve her zamankinden daha hızlı sormayın.

Yedisinden ikisi dur anlamına gelir, ve yüksek sesle söylenmeye değer olan ikisi de bunlardır. unknown_domain'i “henüz değil” sayan bir döngü, hizmetin kendisine ilk kırk milisaniyede söylediği bir şeyi kanıtlamak için tam doksan saniye harcar.

Hangi mesaj sizindir

Bir posta kutusu bir kuyruk değildir, ve içindeki en yeni şey mutlaka beklediğiniz şey değildir. Herkese açık bir alan adında, adresi tahmin eden herkes ona gönderebilir; bir test paketinde aynı adres çalıştırmalar arasında sık sık yeniden kullanılır; ve bir kayıt genellikle iki mesaj gönderir — bir hoş geldin ve bir onay — ve bunlardan yalnızca biri kodu taşır. Çözüm bir referans noktasıdır, ve bu, postaya neden olan şeyden önce alınmalıdır.

  1. Formu göndermeden önce, posta kutusunu limit=1 ile listeleyin ve en yeni mesajın id'sini, boşsa hiçbir şeyi, saklayın. O id, referans noktasıdır.
  2. İşi yapın — formu gönderin, uç noktayı çağırın, düğmeye tıklayın.
  3. Listeyi yoklayın. Mesajlar en yeniden başlayarak döner, bu yüzden en üstten aşağı inin ve referans noktasına ulaştığınız anda durun: oradan aşağısı, işleminizden daha eskidir ve okunmadan göz ardı edilebilir.
  4. Üstünde kalanı filtreleyin, gönderene, konuya, ya da her ikisine göre. Genellikle bir alt dize yeterlidir, ve bu, yerelleştirilmeyecek kısım olmalıdır — “E-postanızı onaylayın” ile eşleşen bir test, test edilen hesap başka bir dile geçtiği gün başarısız olur.
  5. Ondan sonra, ve yalnızca ondan sonra, açın. Listeleme, gövdeyi değil kısa bir preview taşır, ve aradığınız kod çoğunlukla onun sonunun ötesindedir. Bir istek daha, mesajın tamamını getirir, ve bu, listeninkinden ayrı ve çok daha büyük bir bütçeden düşülür.

Bir shell'de, bu iki okuma şöyle görünür — önce referans noktası, sonra yoklama:

shell
curl -sG https://grabmail.io/api/v1/mailbox \
  --data-urlencode "address=signup-42@grabmail.io" --data-urlencode "limit=1"
curl -sG https://grabmail.io/api/v1/mailbox \
  --data-urlencode "address=signup-42@grabmail.io" --data-urlencode "limit=25"

Her iki istek de adresi tam olarak adlandırır, çünkü burada adres, posta kutusunun kendisidir: bir oturum yoktur, sizin adınıza tutulan bir imleç yoktur, ve bir çağrının bir sonrakinin hatırladığı hiçbir şeyi yoktur. Bu, aynı zamanda bir adresin iki yerden aynı anda güvenle izlenebilmesinin de nedenidir — okumak hiçbir şeyi tüketmez, bu yüzden aynı posta kutusundaki iki döngü de her mesajı görür ve hiçbiri diğerinin elinden birini alamaz.

Tam olarak bir kez davranmak

Yeniden denenen bir yoklama, aynı mesajı iki kez görebilir, ve bu nadir bir olay değildir: sunucu yanıtlar, bağlantı gövde size ulaşmadan önce düşer, HTTP istemciniz yeniden dener, ve ikinci yanıt, ilkinin çoktan taşıdığı mesajı içerir. Bir mesajla yaptığınız şey bir bağlantıya tıklamak, bir ödemeyi onaylamak ya da bir kanala göndermekse, bunu iki kez yapmak, sonuçları sürecinizin dışına taşan bir hatadır.

İşlediğiniz id'leri saklayın
Bellekteki bir id kümesi, tek bir fonksiyonun içinde doğup ölen bir bekleme için yeterlidir. Bir yeniden başlatmayı hayatta kalması gereken her şey için — zamanlanmış bir işle boşaltılan bir posta kutusu, birikmiş işleri çözen bir ajan — bunun, kendisiyle birlikte hayatta kalan bir yere yazılması gerekir.
Silmek idempotenttir
Bir mesajı silmek, ikinci seferde de ilk seferde olduğu gibi 200 yanıtı verir, bu yüzden yeniden denenen bir silme hiçbir zaman başarısızlık gibi görünmez ve hiçbir zaman özel bir duruma ihtiyaç duymaz. İşlemi yaptıktan sonra silin, önce değil: ikisi arasındaki bir çökme size, kurtarılamaz olan mesaj yerine, kurtarılabilir olan bir yeniden okumaya mal olur.
Buradaki id, gönderenin Message-ID'si değildir
API'deki id bizimdir: tek bir posta kutusuyla sınırlıdır, ve mesajın süresi dolduğunda var olmayı bırakır. Message-ID başlığı gönderenindir, mesajla birlikte yolculuk eder, ve aynı postayı iki sistem arasında eşleştiriyorsanız istediğiniz odur — başlıklar rehberi nerede bulunacağını söyler.

Bunların hiçbiri, bir kodu bekleyip sonra posta kutusunu atan bir test için gerekli değildir. Bir döngü gözetimsiz çalıştığı anda bunların tamamı gereklidir, çünkü önlediği hata bir hata gibi görünmez: işin, iki kez ve doğru biçimde yapılmış olması gibi görünür.

Beklemenin sunucuya ait olduğu durum

Burada döngüyü sizin yazmadığınız bir yer vardır, ve bu, buna gücü yetmeyen çağıranlar için vardır. Bir yapay zeka ajanı, kontrol etmek için harcadığı her tur için öder, bu yüzden dokuz kez “henüz yok” yanıtı veren bir araç, dokuz boş turdur. MCP sunucusunun wait_for_message'ı, bunun yerine isteği açık tutar, bizim tarafımızda yoklar, ve bir kez yanıtlar — ya mesajla, ya da beklediğini ve hiçbir şey gelmediğini düz bir şekilde belirterek.

bir çağrı, en fazla 25 saniyelik bekleme
$ curl -sX POST https://grabmail.io/mcp -H "Content-Type: application/json" -d '{
  "jsonrpc":"2.0","id":1,"method":"tools/call",
  "params":{"name":"wait_for_message","arguments":{
    "address":"signup-42@grabmail.io","subject_contains":"code",
    "timeout_seconds":25}}}'

Onun üzerine inşa etmeden önce bilmeye değer dört şey vardır.

En fazla 25 saniye bekler
timeout_seconds daha azını isteyebilir, hiçbir zaman daha fazlasını isteyemez. Tavan rastgele değildir: her bekleyen, uyumaktan başka hiçbir şey yapmayan bir worker'dır, ve dakikalarca açık tutulan bir istek, dönmeden çok önce birinin proxy zaman aşımına yenilen bir istektir.
İçeri girerken filtreler
from_contains, subject_contains ve since_id, yukarıdaki bölümle aynı üç karardır, sunucuda verilmiş halde. since_id, referans noktasıdır, ve burada her yerden daha çok önem taşır: o olmadan, çağrı posta kutusunda çoktan oturan her neyse onunla hemen döner.
Bir zaman aşımı bir hata değil, bir yanıttır
Hiçbir şey gelmediğinde, gerçekte ne kadar beklediğiyle birlikte timed_out ayarlanmış olarak döner, ve olduğu gibi, beklemeye devam etmenin yolunun yeniden çağırmak olduğunu söyler. Bir kayıt e-postası için normal bekleme iki ya da üç çağrıdır: döngü budur, ve bu, doksan yerine üç turdur.
8 bekleme yeri vardır, ve kuyruk yoktur
Hepsi doluyken, çağrı yedi başka ajanın arkasında bir sıraya girmek yerine hemen döner ve bunu söyler. Bu doğru başarısızlıktır: “çok fazla bekleme sürüyor” denen bir ajan, posta kutusunu listeleyebilir ve devam edebilir, oysa bir kuyrukta oturan bir ajan yalnızca oturabilir.

Adres başına sınır, onun içinde de geçerlidir — bizim döngümüz de tam olarak sizinki gibi hız sınırına tabidir, bu yüzden sunucu tarafı bir bekleme tabanı aşmanın bir yolu değildir, yalnızca onun bedelini turlarla ödemeyi bırakmanın bir yoludur. Bir test paketi için bunların hiçbiri zahmete değmez: bir test zaten uyumasına izin verilen bir süreçtir, ve testin yazıldığı dildeki bir döngü, uzaktaki birinden çok daha kolay hata ayıklanır. MCP rehberi, araçların gerisini kapsar.

Döngünün tamamı, bir kez

Yukarıdakilerin tamamı, tek bir dosyada: monotonik bir saatten alınan bir son tarih, tek yönlü jitter'la genişleyen bir aralık, dinlenen ve bir deneme sayılmayan Retry-After, neyin yeni olduğuna karar veren bir referans noktası, konu üzerinde bir filtre, ve listelemenin yalnızca önizlediği mesajı getirmek için bir ekstra istek.

tutan bir bekleme
import random
import time
import requests

API     = "https://grabmail.io/api/v1"
ADDRESS = "signup-42@grabmail.io"


def delay(attempt: int) -> float:
    # One second while the message is probably still in flight, then
    # wider. Never below a second: the list endpoint allows one call
    # per second, per address, so jitter is added and never taken off.
    step = 1.0 if attempt < 15 else min(8.0, 2.0 ** (attempt - 14))
    return step + random.uniform(0.0, step / 2)


def watermark(s):
    # Read this BEFORE the form is submitted. Every id above it
    # afterwards is mail that arrived because of what you did.
    r = s.get(f"{API}/mailbox", params={"address": ADDRESS, "limit": 1})
    r.raise_for_status()
    seen = r.json()["messages"]
    return seen[0]["id"] if seen else None


def wait_for(s, subject, since, timeout=120.0):
    deadline = time.monotonic() + timeout
    attempt  = 0
    rejected = set()

    while time.monotonic() < deadline:
        r = s.get(f"{API}/mailbox", params={"address": ADDRESS, "limit": 25})

        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")))
            continue                      # refused, so it was not an attempt
        if r.status_code == 200:
            for m in r.json()["messages"]:        # newest first
                if m["id"] == since:
                    break                 # older than the watermark
                if subject.lower() in m["subject"].lower():
                    full = s.get(f"{API}/message/{m['id']}",
                                 params={"mailbox": ADDRESS})
                    full.raise_for_status()
                    return full.json()
                rejected.add(m["subject"])
        elif r.status_code < 500:
            raise RuntimeError(r.json().get("error", r.status_code))
        # a 5xx falls through: transient, and the deadline still governs

        time.sleep(delay(attempt))
        attempt += 1

    raise TimeoutError(
        f"nothing matching {subject!r} at {ADDRESS} in {timeout:.0f}s; "
        f"saw {sorted(rejected) or 'nothing at all'}")

Bilerek elli küsur satırlık standart kütüphane ve bir HTTP istemcisidir. Kurulacak hiçbir şey, yapılandırılacak hiçbir şey ve içinde hiçbir yerde bir sır yoktur — asıl nokta da bu: aynı şekil, değişmeden Node'a, bir shell betiğine, ya da test framework'ünüzün istek yapmak için zaten kullandığı her neyse ona taşınır.

  1. Referans noktasını, postaya neden olan işlemden önce okuyun, hiçbir zaman sonra değil.
  2. Bir kez hemen sorun, ve yalnızca ondan sonra uyuyun. Asla önce uyumayın.
  3. Son tarihi monotonik bir saatten alın, ve her geçişin başında sınayın.
  4. Aralığı adres başına bir saniyede ya da üstünde tutun, ve jitter'ı yalnızca yukarı doğru ekleyin.
  5. Retry-After'ın söylediği kadar tam olarak uyuyun, ve bir reddi deneme saymayın.
  6. error etiketine göre dallanın: unknown_domain ve invalid_address, dur anlamına gelir, bekle değil.
  7. Gönderene ya da konuya göre eşleştirin, ve referans noktasına ulaştığınızda listede inmeyi durdurun.
  8. Mesajı ayrıştırmadan önce açın — listeleme bir önizleme taşır, gövdeyi değil.
  9. Adresle, filtreyle, bütçeyle, ve döngünün reddettiği konularla başarısız olun.

Dokuz kural, ve sekizi, birinin bir günlükten yeniden kurmak zorunda kaldığı bir başarısızlık yüzünden vardır. Başarısızlıkla ilgili olmayan tek kural ikincisidir: ilk uykudan önce bir kez sormak, çoktan gelmiş bir mesaj için beklemeyi bir saniye yerine dört milisaniyeye indiren şeydir — ki iki yüz testlik bir paket boyunca bu, kimsenin sonradan açıklamak zorunda kalmadığı üç dakikalık bir duvar saatidir.

Sorular

GrabMail'in bir webhook'u var mı?

Hayır, ve bu doldurulmayı bekleyen bir eksiklik değildir. Hizmet postayı alır ve anahtarsız olarak HTTP üzerinden sunar: bir callback eklenecek herkese açık bir adresin arkasında bir hesap yoktur, ve uç noktanızın reddettiği bir teslimatı tutacak bir kuyruk yoktur. İş akışınız gerçekten yoklayamıyorsa, karşılaştırma sayfası, bunu sunan hizmetleri adlandırır.

Bir adresi ne kadar sık yoklamama izin veriliyor?

Adres başına saniyede bir — ve bu, sınırın kenarı değil amaçlanan örüntüdür. Yönetilecek bir günlük kota, bir aylık kota ya da bir burst kredisi yoktur. Bir runner'dan saniyede bir yoklanan yirmi posta kutusu, sıradan bir kullanımdır; tek diğer tavan, tek bir kaynaktan dakikada 1,200 istektir, ki bu tam olarak o yirmisidir, bir yirmi birincisi değil.

Döngüm neden önceki bir test çalıştırmasından bir mesaj döndürdü?

Çünkü ne zaman geldiğini sormadan listedeki ilk kaydı aldı. Bir posta kutusu, kendisine gönderilmiş her şeyi 5 gün boyunca tutar, ve yeniden kullanılan bir adres önceki çalıştırmayla doludur. Postayı tetiklemeden önce en yeni id'yi okuyun ve o id'den aşağısındaki her şeyi göz ardı edin — ya da testin başında posta kutusunun içeriğini silin, ki bu mesaj başına bir istektir ve belirsizliği tamamen ortadan kaldırır.

Boş bir posta kutusu bir 404 mudur?

Hayır. Boş bir posta kutusu, bilerek, count: 0 ve boş bir listeyle 200 döner, ki bir yoklama döngüsünün “henüz yok” için hiçbir zaman özel bir duruma ihtiyacı olmasın. Liste uç noktasından gelen bir 404, alan adının burada barındırılmadığı anlamına gelir; tek bir mesajdan gelen bir 404 ise o id'nin o posta kutusunda olmadığı ya da süresinin dolduğu anlamına gelir.

Bir doğrulama e-postası için ne kadar beklemeliyim?

Otomatik bir testte altmış ile yüz yirmi saniye, ekran başında bekleyen bir kişi için otuz ile altmış saniye. Makine tarafından üretilen postaların çoğu tek haneli saniyelerde gelir; uzun kuyruk, teslimata değil gönderenin kuyruğuna aittir. Bu, düzenli olarak son tarihinize yakınsa, daha uzun bir son tarih cevap değildir — başka bir şey yanlıştır.

İki süreç aynı adresi aynı anda yoklayabilir mi?

Evet. Okumak hiçbir şeyi tüketmez, bu yüzden ikisi de her mesajı görür ve hiçbiri postayı diğerinden gizlemez. Ama o adres için saniyede bir istek bütçesini paylaşırlar, bu yüzden her saniye soran iki döngünün her biri zamanın yaklaşık yarısında reddedilir: her birine iki saniye verin, ya da yoklamayı birine yaptırıp sonuçları diğerine aktarın.

Yoklamalı mıyım, yoksa MCP üzerindeki bekleme aracını mı kullanmalıyım?

Bir test ya da bir betik yazıyorsanız yoklayın: uyumasına izin verilen bir süreç uyumalıdır, ve kendi dilinizdeki bir döngü, uzaktaki birinden daha kolay hata ayıklanır. Çağıran saniye başına değil tur başına ödediğinde wait_for_message'ı kullanın, ki pratikte bu bir yapay zeka ajanı demektir. Çağrı başına en fazla 25 saniye bekler, gönderene ve konuya göre filtreler, ve yalnızca yeniden çağırabileceğiniz düz bir zaman aşımı döndürür.

Henüz tazeyken deneyin

Bir adres tek tıkla alınır, hesap ve kart gerekmez. Bu rehberdeki her şey onunla hemen çalışır.

Tekrar hoş geldiniz

Kutularınız ve alan adlarınız tek bir yerde.