W tym artykule omówimy uruchomienie n8n oraz konfigurację workflow, tak, aby pokazać możliwości automatyzacji poczty e-mail. Skupimy się na przykładzie tzw. śmieciowego adresu e-mail, gdzie przychodzą głównie maile z potwierdzeniami rejestracji i spamem, sporadycznie jakaś wymiana korespondencji. Wbrew intuicji nie będziemy korzystać z AI 🙂 Uwierzcie, bo tak się na prawdę da 🙂
Na czym uruchomimy automatyzację poczty? Jaki sprzęt?
Masz w zasadzie kilka możliwości, jeśli na co dzień pracujesz na systemie Linux możesz rozważyć wykorzystanie tego sprzętu i analiza maili może wykonywać się w tle. Jeśli nie posiadasz serwera, polecam ekstremalnie tani serwer VPS Mikr.us. W związku z tym, że posiadam komputer, na którym mam zainstalowane Xubuntu to go wykorzystam, a więc polecenia będą typowe dla Ubuntu.
Instalacja Dockera i pierwsze uruchomienie n8n
Przygotowanie środowiska (instalacja Docker)
Zależnie od wykorzystanego serwera logujemy się do terminala i wykonujemy polecenia aktualizacji. Jeśli masz już zainstalowanego Dockera przeskocz do uruchamianie n8n na Docker
sudo apt full-upgrade -y
Następnie instalujemy Dockera
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Mamy zainstalowanego Dockera, pozostaje włączyć Dcoker dla użytkownika z pominięciem sudo. (Krok opcjonalny)
sudo usermod -aG docker $USER
newgrp docker
Uruchamianie n8n na Docker
Pora uruchomić n8n z wykorzystaniem Dockera Composer, zastanawiacie się dlaczego? Odpowiedź jest prosta, ponieważ zdefiniujemy wszystko to co jest istotne i każde ewentualne uruchomienie będzie dużo prostsze.
docker-compose.yml
services:
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- "5678:5678"
environment:
- TZ=Europe/Warsaw
- N8N_SECURE_COOKIE=false
- NODE_FUNCTION_ALLOW_BUILTIN=dns
volumes:
- n8n_data:/home/node/.n8n
volumes:
n8n_data:
Pokrótce omówię każdy z elementów docker-compose.yml:
image: n8nio/n8n – odpowiada za wykorzystanie najnowszego obrazu n8n z Docker Hub
restart: always – ponowne uruchomienie kontenera po błędzie i/lub restarcie serwera
ports: – połączenie portu na którym działa kontener i na którym host się łączy. Mogłaby być sytuacja, w której kontener działa na porcie 5678, a host widzi go na 80, w takim przypadku konieczne byłoby zmienienie konfiguracji na „80:5678”
- "5678:5678"
environment: – strefa czasowa pod logi itp.
- TZ=Europe/Warsaw
- N8N_SECURE_COOKIE=false – rozwiązuje problem konieczności korzystania z SSL (https://)
- NODE_FUNCTION_ALLOW_BUILTIN=dns – usunięcie domyślnej blokady DNS
volumes: – przechowywanie danych mimo restartów Dockera
- n8n_data:/home/node/.n8n
volumes: – na dane
n8n_data:
W moim przypadku wykonuję następujące polecenia w terminalu
dominik@Xubuntu:~$ mkdir n8n
dominik@Xubuntu:~$ cd n8n/
dominik@Xubuntu:~/n8n$ nano docker-compose.yml
Polecenia te tworzą katalog n8n i plik docker-compose.yml, w treści którego wpisuję to co powyżej i zapisuję plik. Następnie przchodzodzę do kompilacji i uruchomienia.
docker compose up -d
[+] up 16/16
✔ Image n8nio/n8n:latest Pulled
✔ Network n8n_default Created
✔ Volume n8n_n8n_data Created
✔ Container n8n-n8n-1 Started
Po zobaczeniu podobnego komunikatu przechodzimy do otwarcia http://127.0.0.1:5678

Prawdopodobnie zobaczysz komunikat podobny do powyższego.

Taki widok powinien się pojawić po wypełnieniu formularza i odpowiedzi na pytania co planujemy robić. Teraz gdy już mamy uruchomionego n8n możemy przejść do automatyzacji poczty e-mail.
Automatyzacja poczty e-mail (przenoszenie do folderów)
Opis działania automatyzacji poczty
Zbudujemy automatyzację poczty której zadaniem będzie wychwycić maile, które: w treści miały rozmowy i przenieść do folderu ARCHIWUM, maile które są potwierdzeniem rejestracji i te trafią do folderu REJESTRACJE i ostatni rodzaj maili, które ma wychwycić nasz workflow to SPAM i przenosi do podfloderów w folderze SPAM z datą kiedy dane foldery będą skasowane.
Takie działanie można stosunkowo łatwo rozbuować lub zmienić, a kasowanie maili z opóźnieniem daje czas na reakację.
Budowa przepływu (workflow)
W naszej automatyzacji wykorzystamy przenoszenie maili, więc zmuszeni jesteśmy doinstalować noda, w tym celu wybieramy kolejno Settings -> Community Nodes -> Install a community node i wpisujemy n8n-nodes-imap oraz klikamy Install. Powinniśmy mieć okno podobne do poniższego.

Przechodząc dalej dodajemy Manual Trigger, ułatwi nam to testowanie skryptu. Po tym jak automatyzacja poczty będzie działać poprawnie wymienimy go na inny.
Zaczniemy tworzenia nazw folderów, w których człon nazwy będzie zawierał datę po której będzie kasowany. W tym celu wybieramu Edit Fields(Set), następnie dodajemy zmienną spam_folder_path oraz obliczanie przy pomocy SPAM.DEL_{{ $now.plus({ days: 14 }).toFormat(’yyyy-MM-dd’) }}. Tutaj drobna uwaga Twój serwer pocztowy może wymagać „/” a nie „.” – więc możliwe że trzeba będzie to zmienić.
Kolejny krok to zautomatyzowanie tworzenia folderów w tym celu wybieramy zainstalowany komponent IMAP, jak poniżej.

Kolejno wybieramy Mailbox Create i ustawiamy jak poniżej

Dodatkowo w zakładce Settings ustawiamy On Error jako Continue, dzięki temu jeśli folder istnieje nie będzie to stanowiło problemu.
Operację z dodawaniem Mailbox Create powtarzamy jeszcze dwukrornie tworząc skrzynki jako REJESTRACJE i ARCHIWUM.
Następnie wybieramy Mailbox Get Many, następnie ustawiamy jak poniżej.

Następnie klikamy na Set up credential, uzupełniamy dane do logowania, zapisujemy. Kolejny krok to Execute step i jeśli pojawiają się po prawo maile to znaczy że konfiguracja działa.
Kolejny element jaki trzeba dodać to Loop Over Items, ochroni przed przeciążeniem, ustawmy Batch Size na poziomie 10. Pod wyjście done podpinamy No Operation, do nothing, pod wyjście loop szukamy elementu Code.
Wybieramy język JavaScript i wklejamy kod (wygenerowany przez AI)
// ========================================================================
// KLASYFIKACJA MAILI — spam / rejestracja / rozmowa / inne
// ========================================================================
const dns = require('dns').promises;
// ---------- Konfiguracja ----------
const DNSBL_ZONES = ['zen.spamhaus.org'];
// Mocne, jednoznaczne frazy w temacie wskazujące na POTWIERDZENIE rejestracji
// (celowo nie sprawdzamy samego nadawcy no-reply — to za szeroki sygnał)
const REGISTRATION_SUBJECT_PATTERNS = [
/potwierd[zź].*(rejestracj|konto|adres e-?mail)/i,
/aktywuj (swoje )?konto/i,
/aktywacj[aę] konta/i,
/verify your (email|account)/i,
/confirm your (email|account)/i,
/activate your account/i,
/ustaw (swoje )?has[lł]o/i,
/witamy w/i,
/welcome to/i
];
// ---------- Pomocnicze: nagłówki ----------
function getHeaderText(headers, name) {
const h = headers?.[name];
if (!h) return null;
if (typeof h === 'string') return h;
if (h.text) return h.text;
return null;
}
// ---------- Spam: SpamAssassin ----------
function isSpamAssassinFlagged(headers) {
const status = getHeaderText(headers, 'x-spam-status');
if (!status) return null; // brak nagłówka = brak informacji, sprawdzimy DNSBL
return /^yes/i.test(status.trim());
}
// ---------- Spam: DNSBL (zapasowo, gdy brak nagłówka SpamAssassina) ----------
function extractOriginIp(headers) {
const received = headers?.received;
if (!Array.isArray(received)) return null;
// ostatni element tablicy = najstarszy hop = najbliższy nadawcy
for (let i = received.length - 1; i >= 0; i--) {
const match = received[i].match(/\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]/);
if (match) return match[1];
}
return null;
}
async function checkDnsbl(ip) {
const reversed = ip.split('.').reverse().join('.');
for (const zone of DNSBL_ZONES) {
try {
await dns.resolve4(`${reversed}.${zone}`);
return true; // trafienie = adres na czarnej liście
} catch (e) {
// brak rekordu w tej strefie, próbujemy kolejną / kończymy
}
}
return false;
}
// ---------- Rejestracja ----------
function looksLikeRegistration(subject) {
const s = subject || '';
return REGISTRATION_SUBJECT_PATTERNS.some(p => p.test(s));
}
// ---------- Rozmowa (wątek) ----------
function looksLikeConversation(headers) {
return Boolean(getHeaderText(headers, 'in-reply-to') || getHeaderText(headers, 'references'));
}
// ---------- Nazwa folderu SPAM z datą (Opcja A: z node'a Edit Fields) ----------
const spamFolder = $('Edit Fields').first().json.spam_folder_path;
// ========================================================================
// Główna pętla klasyfikująca
// ========================================================================
const results = [];
for (const item of items) {
const envelope = item.json.envelope || {};
const headers = item.json.headers || {};
const subject = envelope.subject || '';
let category = 'inne';
let reason = '';
let targetFolder = null;
// 1. Spam — najpierw nagłówek SpamAssassina
const saFlag = isSpamAssassinFlagged(headers);
if (saFlag === true) {
category = 'spam';
reason = 'x-spam-status: Yes';
}
// 2. Spam — DNSBL, tylko jeśli brak nagłówka SpamAssassina w ogóle
if (category === 'inne' && saFlag === null) {
const ip = extractOriginIp(headers);
if (ip) {
const hit = await checkDnsbl(ip);
if (hit) {
category = 'spam';
reason = `DNSBL hit (${ip})`;
}
}
}
// 3. Rejestracja
if (category === 'inne' && looksLikeRegistration(subject)) {
category = 'rejestracja';
reason = 'fraza rejestracyjna w temacie';
}
// 4. Rozmowa
if (category === 'inne' && looksLikeConversation(headers)) {
category = 'rozmowa';
reason = 'In-Reply-To / References obecne';
}
// 5. Docelowy folder na podstawie kategorii
if (category === 'spam') targetFolder = spamFolder;
else if (category === 'rejestracja') targetFolder = 'REJESTRACJE';
else if (category === 'rozmowa') targetFolder = 'ARCHIWUM';
// dla 'inne' targetFolder zostaje null — mail zostaje w INBOX
results.push({
json: {
...item.json,
category,
classification_reason: reason,
target_folder: targetFolder
}
});
}
return results;
Następnie dodajemy blok IF, ustawiamy warunek na {{ $json.target_folder }} oraz is not empty. następnie do wyjścia true doczepiamy blok IMAP (MoveEmail). Blok ustawiamy jak poniżej.

Przy tak skonfigurowanej automatyzacji poczty nie pozostaje nic innego niż uruchomić naszą automatyzację.
Działanie workflow

Skoro workflow działa to zostały nam dwie podstawowe rzeczy, pierwsza to wymiana ręcznego klikania na automatyczne działanie oraz zmiana nazwy.
Wymieniamy ręczne klikanie (pierwszy element) na Schedule Trigger, po ustawieniu jak poniżej i połączeniu z kolejnym blokiem uzyskamy uruchamianie co 20minut.

W celu zmiany nazwy klikamy jak poniżej i nadajemy pasującą nam nazwę.

Jeśli mamy zautomatyzowane przenoszenie poczty to pozostała najbardziej ryzykowna część całej automatyzacji, czyli kasowanie poczty po 14dniach.
Automatyzacja poczty e-mail (kasowanie maili)
Wybieramy Create workflow i zaczniemy ponownie od Manual Trigger, ponieważ dobrze mieć kontrolę jeśli w grę wchodzi kasowanie poczty.
Kolejny element to ponownie IMAP, wybieram Mailbox Get Many.
Pora na blok kodu (Code) i kod poniżej.
const results = [];
const today = new Date();
today.setHours(0, 0, 0, 0);
const pattern = /^SPAM\.DEL_(\d{4})-(\d{2})-(\d{2})$/;
for (const item of items) {
const path = item.json.path || '';
const match = path.match(pattern);
if (!match) continue; // interesują nas tylko foldery SPAM.DEL_*
const folderDate = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
if (folderDate <= today) {
results.push({ json: { path, folder_date: folderDate.toISOString() } });
}
}
return results;
Kod ma za zadanie zwrócić nazwę folderu z mailami do skasowania, kolejny blok IMAP z konfiguracją jak poniżej. Zwróć uwagę na limit wiadomości, ponieważ w moim przypadku nie spodziewam się więcej niż kilkudziesięciu maili, postanowiłem ustawić limit dość wysoki. Warto dobrać wartość do sprzętu jakim dysponujemy.

Skoro mamy już pobraną listę maili do skasowania, trzeba się ich pozbyć, więc ostatnim blokiem jest IMAP tylko tym razem DeleteEmail. Konfiguracja ostatniego bloku odpowiedzialnego za kasowanie wygląda jak poniżej.

Uwaga przed testowaniem warto zrobić kopię ponieważ skasowany mail znika :).
Jeśli wszystko działa poprawnie zmieniamy pierwszy blok na Schedule Trigger i ustawiamy np. jedno uruchomienie na dzień, myślę że wystarczy.

Automatyzacja poczty e-mail podsumowanie
Powyższe rozwiązanie i opisy powinny być używane z rozwagą i mają za zadanie pokazać, że automatyzacja poczty nie jest skomplikowana, mi pomogła uporządkować i zautomatyzować sortowanie maili. Dodatkowo pominięte jest wykorzystanie sztucznej inteligencji, ponieważ chciałem pokazać, że są alternatywne rozwiązania. Nie wszystko musi korzystać z AI, zwłaszcza jeśli zmienimy kod i automatyzacja będzie sortować maile na np. pocztę z fakturami, zamówieniami itp. Przy okazji dane nie są wysyłane do nikogo po za naszą sieć.





