Przejdź do treści głównej
UnitAI
Rozbiór inżynierski · Priorytet P0

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.

Na produkcjiFastAPISQLite WALSMTP · TLS fail-closedMCP · OAuth
Kampania „Kawa tygodnia #41” w trakcie wysyłki w panelu UnitMailer: 436 z 1 212 przyjętych przez serwer, kolejka, odrzucone, otwarcia i kliknięcia
Kampania w trakcie wysyłki — postęp liczony z faktycznej kolejki. Dane syntetyczne.
Zweryfikowane metryki

Liczby potwierdzone artefaktami

460 uruchomień
kwalifikacji odpornościowej (2 × 230), w tym SIGKILL po fazie SMTP DATA
0 duplikatów
wysłanych po twardym ubiciu procesu w trakcie transakcji SMTP
494 / 0 FAIL
testów automatycznych silnika kampanii
3 bramki
przed wysyłką: preflight, zatwierdzenie człowieka, globalny wyłącznik
LOG:UNITMAILER_SIGKILL_QUALIFICATION
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
Głębia
01 · Kontekst

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.

02 · Problem

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.

03 · Ograniczenia

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.
04 · Rozwiązanie

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.

05 · Decyzje

Kluczowe decyzje inżynierskie

DETAL INŻYNIERSKI

SQLite 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”.

06 · AI

Agenci AI przez MCP, osobno dla każdej marki

DETAL INŻYNIERSKI

ChatGPT 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.

07 · Trudne problemy

Uczciwe liczby i strefy czasowe

DETAL INŻYNIERSKI

Otwarcia 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.

08 · Hardening

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.
09 · Rezultat

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ć.

Architektura

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.

Architektura UnitMailerOperator w panelu WWW i agent AI przez MCP z OAuth trafiają do aplikacji FastAPI. Rejestr uprawnień i maszyna stanów chronią bazę SQLite WAL. Planista buduje kolejkę wiadomości, worker wysyła przez SMTP marki z TLS fail-closed do skrzynek odbiorców. Globalny wyłącznik może zatrzymać workera.Operatorpanel WWW · sesjaChatGPT / ClaudeMCP · OAuth · 1 markaFASTAPI · JEDEN PROCES APLIKACJIRejestr uprawnieńzdolności per agent i markaMaszyna stanów kampaniiapproved ⇒ zamrożonePreflight i planDNS · szablon · wykluczenialimit dzienny · odstęp · oknoSQLite (WAL)jeden plik · ACIDkolejka wiadomościdziennik audytustan „wysłana” po 250 OKPlanista + workerwznowienie z dyskupo SIGKILL: 0 duplikatówSMTP markihome.pl · OVH · innyTLS fail-closedSkrzynki odbiorcówSPF · DKIM · DMARCGLOBALNY WYŁĄCZNIK WYSYŁKIszkic kampanii · start zatwierdza człowiek
DIAGRAM:UNITMAILER_ARCHITECTURE — przepływ od przygotowania kampanii do skrzynki odbiorcy.
Maszyna stanów kampanii

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.

  1. draft
  2. queue_built
  3. audit_passed
  4. approved
  5. scheduled
  6. sending
  7. completed
  8. ■ edytowalne   ■ zamrożone (INVALID_STATE_TRANSITION)
Granice AI

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ń.

Dowody z panelu

Jak te decyzje wyglądają w interfejsie

Zrzuty z działającej aplikacji uruchomionej na syntetycznej bazie danych (domeny .example).

Gotowość nadawcyWysyłka możliwa dopiero, gdy adres jest potwierdzony, a SPF, DKIM i DMARC poprawne.
Plan dzień po dniuTempo rozpisane na konkretne dni, zanim kampania zostanie zatwierdzona.
Połączenie AI z jedną markąAsystent, marka i profil uprawnień wybierane jawnie; logowanie OAuth.
Zaangażowanie z zastrzeżeniemOtwarcia opisane jako orientacyjne — z wyjaśnieniem, dlaczego.
Co ten projekt dowodziŻe system wysyłkowy można zaprojektować tak, by najgorsza awaria nie skrzywdziła odbiorcy, a AI dostało realną pracę bez realnego ryzyka — bo granice są w architekturze, nie w zaufaniu do modelu.

Masz wdrożenie o podobnej złożoności?

Rozmawiasz bezpośrednio z osobą, która zaprojektowała i wdrożyła ten system.