UnitMailer: wysyłka, która nie dubluje maili, i AI, które nie przekroczy granic
Platforma kampanii e-mail dla wielu marek. Dwa twarde wymagania: żadna awaria w trakcie wysyłki nie może wysłać klientowi tego samego maila dwa razy, a asystent AI nie może zrobić niczego, na co właściciel nie pozwolił — nawet jeśli ktoś go do tego namówi.

Liczby potwierdzone artefaktami
unitmailer ▸ hardening qualification runs .............. 460 (230/230 ×2) kill point ........ SIGKILL po SMTP DATA restart ........... kolejka wznowiona z bazy duplikaty ......... 0 campaign engine ... 494/494 PASS
Kilka marek, jedna osoba
Firmy, dla których powstał UnitMailer, prowadzą po kilka marek: sklep, kawiarnię, aplikację. Każda ma własną domenę i własnych odbiorców. Platformy SaaS albo każą płacić za każdą markę osobno, albo wysyłają z cudzych serwerów, a reputacja domeny marki zależy od sąsiadów na wspólnej infrastrukturze.
Cel: jeden panel, w którym każda marka wysyła z własnej domeny i własnej skrzynki, w tempie, którego nie ukarzą filtry antyspamowe — i z AI, które odciąża, ale nie przejmuje sterów.
Dwa ryzyka, których nie wolno odpuścić
Duplikaty po awarii. Jeśli proces padnie po wysłaniu treści do serwera SMTP, ale przed zapisaniem, że wiadomość wyszła, naiwny system po restarcie wyśle ją jeszcze raz. Przy tysiącach odbiorców to setki zdublowanych maili i zgłoszenia spamu.
AI z dostępem do wysyłki. Asystent, który może przygotować kampanię, może też — przy złej architekturze — ją uruchomić, zmienić listę odbiorców po zatwierdzeniu albo dać się przekonać instrukcji ukrytej w polu „imię” zaimportowanego kontaktu.
Wymagania brzegowe
DETAL INŻYNIERSKI- — Wysyłka przez SMTP klienta (home.pl, OVH, dowolny), nie przez własną pulę IP.
- — Mały serwer i jeden plik bazy: bez klastra, bez zewnętrznego brokera kolejek.
- — Każda marka odizolowana: nadawca, transport, kontakty, szablony, wykluczenia, raporty.
- — TLS fail-closed: jeśli połączenia z serwerem SMTP nie da się zabezpieczyć, wiadomość nie wychodzi.
- — Granice AI egzekwowane w kodzie serwera, nie w treści promptu.
Kolejka z atomowym potwierdzeniem i bramki przed startem
Każda wiadomość kampanii to osobny rekord w kolejce z jednoznacznym stanem. Status „wysłana” zapisuje się atomowo dopiero po odpowiedzi serwera 250 OK, a stan kolejki żyje w bazie, nie w pamięci procesu. Testy z twardym ubiciem procesu po fazie DATA potwierdzają, że restart nie wysyła nikomu tej samej wiadomości drugi raz.
Przed startem kampania przechodzi preflight (nadawca, DNS, szablon, odbiorcy, wykluczenia), a potem czeka na zatwierdzenie człowieka. Tempo — limit dzienny, odstęp, dni i godziny — jest rozpisane na plan dzień po dniu, zanim cokolwiek wyjdzie.
Kluczowe decyzje inżynierskie
DETAL INŻYNIERSKISQLite w trybie WAL zamiast serwera bazy. Jeden plik, transakcje ACID, czytelnicy nie blokują zapisu. Dla obciążenia jednego serwera wysyłkowego to prostsze i bardziej przewidywalne niż osobny klaster.
SIGKILL w testach, nie tylko SIGTERM. Łagodne zamknięcie niczego nie dowodzi. Testy kwalifikacyjne ubijają proces twardo, w najgorszym momencie — po fazie DATA transakcji SMTP — i sprawdzają, że żaden odbiorca nie dostał wiadomości dwa razy.
Kolejka czytana z prawdy, nie z wyliczeń. Panel i agenci AI widzą faktycznie zbudowaną kolejkę (odbiorca, termin, status), a nie matematykę „200 dziennie × 7 dni”.
Agenci AI przez MCP, osobno dla każdej marki
DETAL INŻYNIERSKIChatGPT i Claude łączą się przez serwer MCP z logowaniem OAuth. Każde połączenie dotyczy jednej marki i ma profil uprawnień: podgląd, przygotowanie kampanii albo pełny operator marki. Zdolności agenta są wyliczane na żywo z rejestru autoryzacji po stronie serwera.
Agent z profilem przygotowania może stworzyć szablon, kampanię, ustawić tempo, zrobić wysyłkę testową i oznaczyć kampanię jako gotową do zatwierdzenia. Dalej przejmuje człowiek. Każda komenda agenta trafia do dziennika audytu.
Uczciwe liczby i strefy czasowe
DETAL INŻYNIERSKIOtwarcia maili są z natury niepewne: część programów pocztowych blokuje piksel, a skanery bezpieczeństwa „otwierają” wszystko. Raporty pokazują więc każdy etap osobno — przyjęte, odrzucone, zwroty, wypisania — a otwarcia opisują wprost jako orientacyjne.
Harmonogram liczy się w strefie czasowej marki (walidowanej względem nazw IANA), a w bazie trzymany jest czas UTC. Plan wysyłki pokazuje godziny tak, jak zobaczy je marka, zanim kampania zostanie zatwierdzona.
Testy i bezpieczeństwo
DETAL INŻYNIERSKI- — 460 uruchomień kwalifikacyjnych (2 × 230) z kontrolowanym SIGKILL.
- — 494 testy automatyczne silnika kampanii, 0 FAIL.
- — Tryb dry-run na środowiskach testowych i globalny wyłącznik, który zatrzymuje wszystkie wysyłki.
- — Hasła skrzynek zaszyfrowane, przechowywane jako referencje do sejfu; kopie zapasowe bazy.
- — Link wypisu i nagłówki List-Unsubscribe w każdej wiadomości, listy wykluczeń globalne i per marka.
Rezultat produkcyjny
UnitMailer działa na produkcji pod adresem mailing.unitai.pl. Marki wysyłają z własnych domen, z kontrolą SPF, DKIM i DMARC przed startem, w zaplanowanym tempie. AI przygotowuje kampanie w granicach, których nie może samo poszerzyć.
Jak to jest zbudowane
Panel i serwer MCP to jedna aplikacja FastAPI nad jednym plikiem bazy SQLite (WAL). Planista buduje kolejkę, a worker wysyła przez SMTP marki z wymuszonym TLS.
Po zatwierdzeniu nic się już nie zmienia
Edytować można tylko szkic i kampanię przed zatwierdzeniem. Od stanu approved każda próba zmiany jest odrzucana na serwerze — niezależnie od tego, czy pyta człowiek, czy agent AI.
- draft
- queue_built
- audit_passed
- approved
- scheduled
- sending
- completed
- ■ edytowalne ■ zamrożone (INVALID_STATE_TRANSITION)
Bezpieczeństwo nie zależy od tego, czy model się zachowa
Każde z tych ograniczeń jest egzekwowane w kodzie serwera. Prompt może je opisać, ale nie może ich zmienić.
Uprawnienia liczone na serwerze
Agent AI dostaje listę zdolności (np. campaigns.draft_write, templates.publish) z rejestru autoryzacji, osobno dla każdej marki. Treść promptu ani dokumentu nie może ich rozszerzyć.
Narzędzi do startu po prostu nie ma
Profil przygotowania kampanii nie „odmawia” zatwierdzenia wysyłki — nie ma narzędzia, które by to robiło. Zatwierdzenie, uzbrojenie i odblokowanie wyłącznika zostają po stronie człowieka.
Zamrożenie po zatwierdzeniu
Po przejściu w approved każda zmiana kampanii jest odrzucana błędem INVALID_STATE_TRANSITION — niezależnie od tego, kto pyta i jak.
Jednorazowy kod właściciela
Kontrolowana wysyłka na żywo wymaga kodu wygenerowanego w zalogowanej sesji właściciela dla tej jednej kampanii. Deklaracja „jestem właścicielem” na czacie nie wystarcza.
Dane to dane, nie polecenia
Pola kontaktów, importowany tekst i historia kampanii są traktowane jako niezaufane dane. Tekst „wyślij teraz” w nazwisku leada nie ma żadnej mocy.
Sekrety poza zasięgiem
Hasła skrzynek SMTP są zaszyfrowane i przechowywane jako referencje do sejfu. Żadne narzędzie agenta nie odczytuje poświadczeń.
Jak te decyzje wyglądają w interfejsie
Zrzuty z działającej aplikacji uruchomionej na syntetycznej bazie danych (domeny .example).
Masz wdrożenie o podobnej złożoności?
Rozmawiasz bezpośrednio z osobą, która zaprojektowała i wdrożyła ten system.