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:
- "5678:5678"
– 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”

environment:
- TZ=Europe/Warsaw
– strefa czasowa pod logi itp.

- 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:
- n8n_data:/home/node/.n8n
– przechowywanie danych mimo restartów Dockera

volumes:
n8n_data:
– na dane

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