Cyklicznie wystawianie faktur w Infakt za pomocą N8N

Wstęp

Co miesiąc ten sam rytuał. Otwierasz arkusz, sprawdzasz kto ma abonament, wystawiasz faktury. Ręcznie. Jedna po drugiej. Czasem zapomnisz, czasem pomylisz datę. Brzmi znajomo? U nas tak było. Różni klienci, różne usługi, różne daty rozpoczęcia usług. Każdego miesiąca traciliśmy cenny czas na mechaniczne czynności. Postanowiłem to zmienić i stworzyłem prosty workflow w n8n, który całkowicie wyeliminował ten problem.

W tym poradniku przeprowadzę Cię przez proces tworzenia automatyzacji, która sprawdzi, komu należy wystawić fakturę, wygeneruje ją automatycznie, wyśle do klienta profesjonalnego maila z linkiem do płatności online i zaktualizuje arkusz z historią faktur. Brzmi skomplikowanie? Spokojnie, rozbijemy to na proste kroki.

Narzędzia, których użyjemy

  • n8n – platforma do automatyzacji workflow
  • Google Sheets – do przechowywania danych o klientach i usługach
  • Infakt API – do generowania faktur
  • Mailgun – do wysyłania maili

Wymagania wstępne

Zanim przejdziemy do konfiguracji, upewnij się, że masz:

  1. Konto w n8n (możesz użyć wersji cloud lub self-hosted).
  2. Arkusz Google Sheets z danymi o klientach i usługach.
  3. Konto w Infakt z dostępem do API (potrzebny klucz API).
  4. Konto w Mailgun (lub inna usługa do wysyłania maili) (potrzebny klucz API).
  5. Podstawową znajomość n8n – jeśli jesteś początkujący, nie martw się, wyjaśnię wszystko krok po kroku.

Konfiguracja Arkusza Google Sheets

Pierwszym krokiem jest przygotowanie arkusza Google Sheets, który będzie przechowywał dane o Twoich klientach i usługach. Arkusz powinien mieć dwie zakładki:

  1. Usługi – tu przechowujemy informacje o klientach i ich usługach.
  2. Historia_faktur – tu będziemy zapisywać historię wystawionych faktur.

Struktura zakładki „Usługi”

Oto jak powinna wyglądać zakładka „Usługi”:

  • ID – unikalny identyfikator usługi.
  • Nazwa_firmy – nazwa firmy klienta.
  • E_mail – adres email klienta.
  • Nazwa_usługi – nazwa usługi z placeholderami, np. „Usługa testowa – abonament %month% %year%”.
  • Cena_netto – cena netto usługi.
  • VAT – stawka VAT (np. 23).
  • Cykl – cykl rozliczeniowy (miesięczny lub roczny).
  • Data_rozpoczęcia – data rozpoczęcia usługi.
  • Ostatnia_faktura – data ostatniej wystawionej faktury.
  • Status – status usługi (aktywna lub nieaktywna).

Przykład danych w Google Sheets

Zrzut ekranu przedstawia arkusz Google Sheets z zakładką „Usługi”.
Ten arkusz jest podstawą automatyzacji, dostarczając dane do generowania faktur.

Konfiguracja n8n

Teraz przejdziemy do n8n, gdzie stworzymy workflow automatyzujący cały proces. Workflow składa się z 11 node-ów, które wykonują poszczególne zadania: od sprawdzenia daty, przez wystawienie faktury, po wysłanie maila do klienta.

Oto jak wygląda gotowy workflow:

Zrzut ekranu przedstawia workflow „Automatyczne wystawianie faktur w Dogtronic” w n8n.

Krok 1 – utwórz nowy workflow

Zaloguj się do n8n i utwórz nowy workflow. Nazwij go np. „Automatyczne wystawianie faktur”.

Krok 2 – dodaj node Schedule Trigger

Ten node uruchomi workflow codziennie o 8:00 rano.

  • Typ: Schedule Trigger.
  • Konfiguracja: Ustaw Trigger at na 8:00 AM.
  • Cel: Codzienne sprawdzanie, czy należy wystawić nowe faktury.

Krok 3 – dodaj node Google Sheets (get_invoices)

Ten node pobierze dane z arkusza „Usługi”.

  • Typ: Google Sheets.
  • Operacja: Read.
  • Document ID: ID Twojego arkusza Google Sheets.
  • Sheet Name: „Usługi”.
  • Autentykacja: Użyj OAuth2 dla Google Sheets.
  • Cel: Pobranie listy usług i klientów z arkusza.

Krok 4 – dodaj node Code (check_date)

Ta noda zawiera skrypt JavaScript, który sprawdzi, dla których usług minął miesiąc lub rok od ostatniej faktury i przygotuje dane do wystawienia nowych faktur.

Przykładowy kod JavaScript:



const today = new Date();
// Ustawiamy godzinę na koniec dnia dla precyzyjniejszego porównania
today.setHours(23, 59, 59, 999);

const items = $input.all();
const invoicesToCreate = [];

// Pobierz aktualny miesiąc i rok
const currentMonth = today.toLocaleString('pl-PL', { month: 'long' }); // styczeń, luty, etc.
const currentYear = today.getFullYear(); // 2025

// Funkcja do bezpiecznego obliczania następnej daty faktury
function calculateNextInvoiceDate(lastInvoiceDate, cycle) {
  const lastDate = new Date(lastInvoiceDate);
  
  if (cycle === 'miesięczny') {
    // Pobierz dzień miesiąca z ostatniej faktury
    const dayOfMonth = lastDate.getDate();
    
    // Utwórz nową datę o miesiąc później
    const nextDate = new Date(lastDate.getFullYear(), lastDate.getMonth() + 1, 1);
    
    // Sprawdź ile dni ma następny miesiąc
    const daysInNextMonth = new Date(nextDate.getFullYear(), nextDate.getMonth() + 1, 0).getDate();
    
    // Ustaw dzień - jeśli oryginalny dzień jest większy niż liczba dni w następnym miesiącu,
    // ustaw na ostatni dzień miesiąca
    const targetDay = Math.min(dayOfMonth, daysInNextMonth);
    nextDate.setDate(targetDay);
    
    return nextDate;
  } else if (cycle === 'roczny') {
    const nextDate = new Date(lastDate);
    nextDate.setFullYear(nextDate.getFullYear() + 1);
    return nextDate;
  }
  
  return null;
}

// Funkcja do formatowania daty dla logów
function formatDate(date) {
  return date.toISOString().split('T')[0];
}

console.log(`=== SPRAWDZANIE FAKTUR NA DZIEŃ: ${formatDate(today)} ===`);

for (const item of items) {
  const data = item.json;
  
  console.log(`\n--- Sprawdzanie usługi: ${data.Nazwa_usługi} (ID: ${data.ID}) ---`);
  
  if (data.Status !== 'aktywna') {
    console.log(`Status: ${data.Status} - pomijam`);
    continue;
  }
  
  if (!data.Ostatnia_faktura) {
    console.log(`Brak daty ostatniej faktury - pomijam`);
    continue;
  }
  
  const lastInvoice = new Date(data.Ostatnia_faktura);
  const startDate = new Date(data.Data_rozpoczęcia);
  
  console.log(`Ostatnia faktura: ${formatDate(lastInvoice)}`);
  console.log(`Cykl: ${data.Cykl}`);
  
  // Sprawdź czy data ostatniej faktury jest prawidłowa
  if (isNaN(lastInvoice.getTime())) {
    console.log(` Nieprawidłowa data ostatniej faktury: ${data.Ostatnia_faktura} - pomijam`);
    continue;
  }
  
  const nextInvoiceDate = calculateNextInvoiceDate(lastInvoice, data.Cykl);
  
  if (!nextInvoiceDate) {
    console.log(` Nieobsługiwany cykl: ${data.Cykl} - pomijam`);
    continue;
  }
  
  console.log(` Następna faktura powinna być: ${formatDate(nextInvoiceDate)}`);
  console.log(` Dzisiaj: ${formatDate(today)}`);
  console.log(` Czy czas na fakturę? ${nextInvoiceDate <= today ? 'TAK' : 'NIE'}`);
  
  if (nextInvoiceDate <= today) {
    let serviceName;
    
    // Sprawdź czy istnieje numer zamówienia
    if (data.Numer_zamowienia && data.Numer_zamowienia.trim() !== '') {
      // Jeśli jest numer zamówienia, użyj tego formatu
      serviceName = `Usługi zgodnie zamówieniem nr ${data.Numer_zamowienia}`;
    } else {
      // Jeśli nie ma numeru zamówienia, użyj standardowej nazwy z placeholderami
      // Pobierz miesiąc i rok z daty następnej faktury
      const invoiceMonth = nextInvoiceDate.toLocaleString('pl-PL', { month: 'long' });
      const invoiceYear = nextInvoiceDate.getFullYear();

      // Zamień %month% i %year% w nazwie usługi
      serviceName = data.Nazwa_usługi
        .replace('%month%', invoiceMonth)
        .replace('%year%', invoiceYear);
    }
    
    const invoice = {
      serviceId: data.ID,
      clientId: data.Klient_ID,
      client_company_name: data.Nazwa_firmy,
      client_email: data.E_mail,
      serviceName: serviceName,
      price: data.Cena_netto,
      vat: data.VAT,
      cycle: data.Cykl,
      // Dodaj dodatkowe informacje do debugowania
      lastInvoiceDate: formatDate(lastInvoice),
      nextInvoiceDate: formatDate(nextInvoiceDate),
      todayDate: formatDate(today)
    };
    
    invoicesToCreate.push(invoice);
    console.log(`FAKTURA DO UTWORZENIA dla ${data.Nazwa_firmy}`);
  } else {
    console.log(`Jeszcze nie czas na fakturę`);
  }
}



if (invoicesToCreate.length > 0) {
  console.log(`\n Lista faktur do utworzenia:`);
  invoicesToCreate.forEach((invoice, index) => {
    console.log(`${index + 1}. ${invoice.client_company_name} - ${invoice.serviceName}`);
  });
}

return invoicesToCreate.map(invoice => ({ json: invoice }));

Dynamiczne nazwy usług – mały trick, duża oszczędność
Zamiast ręcznie edytować nazwę usługi co miesiąc, używam placeholderów:
W arkuszu wpisuję: Usługa testowa - abonament %month% %year%.
System automatycznie zamienia to na: Usługa testowa – abonament czerwiec 2025.

Cel: Sprawdzenie, które usługi wymagają wystawienia faktury i przygotowanie danych z dynamiczną nazwą usługi (np. "Usługa testowa - abonament czerwiec 2025").

Krok 5 – Dodaj node If

Ten node sprawdzi, czy istnieją FV do wystawienia.

  • Warunek: Sprawdź, czy client_email istnieje i nie jest pusty.
  • Cel: Upewnienie się, że faktura zostanie wysłana tylko do klientów z poprawnym adresem email.

Krok 6 – dodaj node Split in Batches

Ten node podzieli listę faktur do wystawienia na pojedyncze elementy, aby przetwarzać je po kolei.

  • Konfiguracja: Ustaw batch size na 1.
  • Cel: Przetwarzanie faktur jedna po drugiej z opóźnieniem, aby uniknąć problemów z numeracją faktur w Infakt.

Krok 7 – dodaj nodę HTTP Request (create_invoice)

Ten node wyśle żądanie POST do API Infakt w celu utworzenia nowej faktury.

  • URL: https://api.infakt.pl/v3/invoices.json.
  • Metoda: POST.
  • Autentykacja: Header Auth z kluczem API Infakt.
  • Body (JSON):
{
  "invoice": {
    "kind": "vat",
    "number": null,
    "sell_date": "{{$json.nextInvoiceDate}}",
    "invoice_date": "{{$json.nextInvoiceDate}}",
    "payment_to": "{{DateTime.fromISO($json.nextInvoiceDate).plus({days: 7}).toFormat('yyyy-MM-dd')}}",
    "client_company_name": "{{ $json.client_company_name }}",
    "services": [
      {
        "name": "{{$json.serviceName}}",
        "net_price": "{{Math.round($json.price * 100)}}",
        "unit_net_price": "{{Math.round($json.price * 100)}}",
        "tax_symbol": "{{$json.vat}}",
        "quantity": 1
      }
    ]
  }
}
Cel: Utworzenie nowej faktury w Infakt z dynamicznymi danymi.

Krok 8 – Dodaj node Google Sheets (update_sheet)

Po wystawieniu faktury zaktualizuj arkusz „Usługi” z nową datą ostatniej faktury.

  • Operacja: Update.
  • Sheet Name: Usługi.
  • Columns to update: Ostatnia_faktura z wartością {{ $json.nextInvoiceDate }}.
  • Matching column: ID z wartością {{ $json.serviceId }}.
  • Cel: Aktualizacja daty ostatniej faktury w arkuszu.

Krok 9 – Dodaj node Google Sheets (add_to_history)

Dodaj rekord o wystawionej fakturze do zakładki Historia_faktur.

  • Operacja: Append.
  • Sheet Name: „Historia_faktur”.
  • Columns:
    • ID: {{ $('create_invoice').item.json.uuid }}.
    • Data_dodania: {{DateTime.now().toFormat('yyyy-MM-dd H:mm')}}.
    • Klient_ID: {{ $json.client_company_name }}.
    • Numer_faktury: {{ $('create_invoice').item.json.number }}.
    • Data_wystawienia: {{ $('create_invoice').item.json.invoice_date }}.
    • Kwota_netto: {{ $('create_invoice').item.json.net_price / 100 }}.
    • Status: {{ $('create_invoice').item.json.status }}.
    • Url: {{ $('create_invoice').item.json.extensions.payments.link }}.
  • Cel: Zapisanie historii wystawionych faktur.

Krok 10 – Dodaj node HTTP Request (download_invoice)

Pobierz PDF faktury z Infakt.

  • URL: https://api.infakt.pl/v3/invoices/{{ $('create_invoice').item.json.uuid }}/pdf.json?document_type=original.
  • Metoda: GET.
  • Autentykacja: Header Auth z kluczem API Infakt.
  • Cel: Pobranie PDF faktury do załączenia w mailu.

Krok 11 – Dodaj node Mailgun (send_mail)

Wyślij maila do klienta z fakturą i linkiem do płatności.

  • From: Twój adres skrzynki nadawczej.
  • To: {{ $json.client_email }}.
  • CC: Adres np. zarządu.
  • Subject: Nowa faktura do zapłaty ({{ $('create_invoice').item.json.number }}).
  • HTML Body: Profesjonalnie sformatowany mail z danymi faktury i przyciskiem „ZAPŁAĆ TERAZ”.
  • Attachments: PDF faktury.

Przykład maila wysłanego do klienta:

Przykład wiadomość e-mail, którą otrzymuje klient.

Fragment HTML maila:

<p>Dzień dobry,</p>
<p>W załączeniu przesyłamy fakturę:</p>
<ul>
  <li><strong>Numer faktury:</strong> {{ $('create_invoice').item.json.number }}</li>
  <li><strong>Usługa:</strong> {{ $json.serviceName }}</li>
  <li><strong>Data wystawienia:</strong> {{ $('create_invoice').item.json.invoice_date }}</li>
  <li><strong>Termin płatności:</strong> {{ $('create_invoice').item.json.payment_date }}</li>
  <li><strong>Kwota do zapłaty:</strong> {{ $('create_invoice').item.json.gross_price / 100 }} PLN</li>
</ul>
<div class="payment-section">
  <p>Zapłać wygodnie i bezpiecznie online:</p>
  <a href="{{ $('add_to_history').item.json.Url }}" style="display: inline-block; background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold; margin: 15px 0; text-align: center;">ZAPŁAĆ TERAZ</a>
  <p><small>Bezpieczne płatności obsługiwane przez Infakt.pl</small></p>
</div>
<p>Dziękujemy za terminową płatność i dotychczasową współpracę! 🙏</p>
<p>W razie pytań jesteśmy do Państwa dyspozycji.</p>
<hr>
<p><small>Ta wiadomość została wygenerowana automatycznie. Prosimy nie odpowiadać na ten email. W razie wątpliwości prosimy o kontakt na adres hello@dogtronic.io</small></p>
Cel: Wysłanie maila do klienta z fakturą i linkiem do płatności.

Krok 12 – Dodaj node Wait

Wprowadź opóźnienie 1 sekundy przed przetworzeniem kolejnej faktury.

  • Amount: 1 second.
  • Cel: Zapobieganie problemom z numeracją faktur w Infakt przez zbyt szybkie wysyłanie żądań.

Testowanie Workflow

Po skonfigurowaniu wszystkich nodów, przetestuj workflow z danymi testowymi:

  1. Upewnij się, że w arkuszu „Usługi” masz co najmniej jeden wiersz z datą ostatniej faktury, która wymaga wystawienia nowej faktury.
  2. Uruchom workflow manualnie, aby sprawdzić, czy faktura jest poprawnie tworzona i wysyłana.
  3. Sprawdź logi w n8n, aby upewnić się, że nie ma błędów.
  4. Zweryfikuj, czy arkusz „Historia_faktur” został zaktualizowany.
  5. Sprawdź, czy mail został wysłany poprawnie z załącznikiem PDF.

Typowe problemy i rozwiązania:

  • Błąd autentykacji API: Upewnij się, że klucze API są poprawne.
  • Nieprawidłowe daty: Sprawdź format dat w arkuszu i w kodzie JavaScript.
  • Problemy z numeracją faktur: Upewnij się, że node Wait jest skonfigurowana poprawnie.

Podsumowanie

Gratulacje! Właśnie stworzyłeś automatyzację, która oszczędzi Ci masę czasu i zminimalizuje ryzyko błędów w procesie wystawiania faktur. Workflow działa sam, bez Twojej interwencji, a Ty możesz skupić się na ważniejszych zadaniach.

Korzyści

  • Zero pomyłek w datach i kwotach.
  • Automatyczne wysyłanie maili z linkiem do płatności.
  • Pełna historia faktur w arkuszu.
  • Zarząd na bieżąco z informacjami (dzięki CC w mailu).

Zachęcam do dostosowania tego workflow do swoich specyficznych potrzeb. Możesz dodać więcej logiki, np. obsługę różnych walut czy automatyczne przypomnienia o zaległych płatnościach.

Chcesz więcej automatyzacji? Sprawdź nasz wpis o automatycznym oznaczaniu opłaconych faktur.


Dodatkowe zasoby

Pamiętaj, że ten poradnik jest punktem wyjścia. Automatyzacja to potężne narzędzie, które możesz rozwijać i dostosowywać do swoich potrzeb.

Powodzenia 🐶.

TLDR

Nie masz teraz czasu na pełną lekturę? Poniżej znajdziesz podsumowanie najważniejszych wniosków z całego artykułu.

  • Sprawdza codziennie o 8:00, komu należy wystawić fakturę, generuje ją w Infakt, wysyła maila z PDF i linkiem do płatności, a następnie aktualizuje arkusz Google Sheets z historią faktur.

  • n8n (do automatyzacji), Google Sheets (dane klientów), Infakt API (faktury), Mailgun (wysyłka maili) oraz klucze API do wszystkich usług.

  • Około 2-3 godzin na pierwsze skonfigurowanie wszystkich 11 node-ów w n8n, plus czas na przygotowanie arkusza Google Sheets.

  • JavaScript porównuje datę ostatniej faktury z obecną datą, uwzględniając cykl rozliczeniowy (miesięczny/roczny) i automatycznie oblicza, czy czas na nową fakturę.

  • System automatycznie zamienia placeholdery %month% %year% w nazwach usług, np. „Abonament %month% %year%” staje się „Abonament czerwiec 2025”.

  • Tak, workflow można uruchomić manualnie z danymi testowymi, sprawdzić logi w n8n i zweryfikować czy wszystkie kroki działają poprawnie.

  • Każda wystawiona faktura jest automatycznie zapisywana w arkuszu „Historia_faktur” z numerem, datą, kwotą i linkiem do płatności online.

  • Tak, automatycznie generowane maile zawierają szczegóły faktury, załącznik PDF i kolorowy przycisk „ZAPŁAĆ TERAZ” prowadzący do bramki płatniczej.

  • Workflow ma wbudowane opóźnienie 1 sekundy między fakturami i przetwarza je pojedynczo, co zapobiega konfliktom w API Infakt.

  • Zero ręcznej pracy, brak pomyłek w datach/kwotach, automatyczne maile z płatnościami online, pełna historia w arkuszu i zarząd informowany na bieżąco.

Marek Golan

CEO Dogtronic, Prezes ESTA Cluster, dumny opiekun Borysa. Ma prawie 20 lat doświadczenia w branży IT zarówno jako programista jak i właściciel firm technologicznych.

Przekształć
swoje pomysły
w rzeczywistość

Skontaktuj się z nami i pozwól nam sprawdzić jak możemy Ci pomóc.

Najciekawsze treści na Blogu

Zobacz wszystkie

Automatyczne oznaczanie opłaconych faktur w inFakt

Jeśli prowadzisz firmę i wystawiasz faktury, zapewne znasz ten ból – codzienne sprawdzanie, które faktury zostały opłacone i ręczne oznaczanie ich w systemie. Ja przez lata robiłem to samo, aż w końcu stworzyłem automatyzację, która robi to za mnie. Dziś podzielę się moim rozwiązaniem.

Problem – ręczne oznaczanie faktur

Moja codzienna rutyna wyglądała tak:

  1. NestBank wysyłał mi maila w stylu: „Dzień dobry, dnia 01.07.2025 nastąpił wpływ 19500,00 PLN na konto BIZnest Konto 56187010452078105478980001, od Acme Spółka z ograniczoną odpowiedzialnością, tytułem 12/06/2025”
  2. Czytałem maila
  3. Logowałem się do inFakt
  4. Szukałem faktury 12/06/2025
  5. Weryfikowałem dane
  6. Oznaczałem jako opłaconą

Brzmi prosto? No właśnie. Problem w tym, że trzeba to robić codziennie, a czasem kilka razy dziennie. I jak człowiek się spieszy albo ma gorszy dzień, to się zdarza, że jakąś fakturę pominie. Efekt? Niepotrzebnie ruszamy proces windykacyjny do klienta, który już zapłacił. Niezręcznie.

Rozwiązanie – automatyczny workflow

Stworzyłem prosty workflow, który:

  • Automatycznie czyta maile od banku z powiadomieniami o wpłatach
  • Używa AI do wyciągnięcia kluczowych danych (numer faktury, klient, kwota)
  • Szuka pasującej faktury w bazie inFakt
  • Zmienia jej status na „opłacona” przez API
  • Wysyła mi powiadomienie, że wszystko poszło OK

Czemu nie ma prostszej integracji?

Niestety, inFakt nie oferuje bezpośredniej integracji z NestBank. Gdyby oferował, pewnie bym z niej skorzystał.

Po co AI do parsowania maili?

Tu przyznaję się do lenistwa. Mogłem napisać regex’y do wyciągania danych z maili, ale:

  • AI radzi sobie z tym błyskawicznie
  • Nie muszę się martwić o różne formaty maili
  • Koszt API jest praktycznie niezauważalny przy tej skali
  • Jeśli bank zmieni format maila, wystarczy dostosować prompt

Jak wygląda workflow?

Na załączonym schemacie widać cały przepływ:

  1. Gmail Trigger – nasłuchuje na maile z odpowiednią etykietą
  2. Basic LLM Chain – AI parsuje treść maila i wyciąga dane
  3. Payment – formatuje dane do dalszego przetwarzania
  4. Get_invoices – pobiera listę niezapłaconych faktur z Google Sheets (musisz wcześniej zrobić integrację do pobierania FV z Infakt do Google Sheets)
  5. Check_if_paid – sprawdza czy znaleziono pasującą fakturę
  6. If – decyzja: znaleziono czy nie?
  7. Infakt_mark_as_paid – oznacza fakturę jako opłaconą przez API
  8. Send_notification – wysyła powiadomienie o sukcesie
  9. Mark_message_as_read – oznacza maila jako przeczytany

Czy warto?

Absolutnie tak. Jedna powtarzalna, irytująca czynność mniej. Zero pomyłek. Zero stresu, że coś pominąłem. Wszystko dzieje się automatycznie w tle.

Jak samemu stworzyć taki work-flow w N8N?

Wymagania wstępne

Zanim zaczniemy, upewnij się że masz:

  • Działającą instancję n8n (self-hosted lub cloud)
  • Konto Google z dostępem do Gmail i Google Sheets
  • Konto inFakt z dostępem do API
  • Konto OpenRouter (do AI) lub inny model LLM
  • Konto Mailgun (opcjonalnie – do powiadomień)

Krok 1: Przygotowanie Google Sheets

Najpierw potrzebujemy arkusza z listą faktur:

  1. Stwórz nowy arkusz Google Sheets nazwany np. „Faktury: [Twoja Firma] 2025”
  2. Stwórz arkusz o nazwie „Infakt”
  3. Dodaj kolumny:
    • ID – unikalny identyfikator
    • Numer faktury – np. „12/06/2025”
    • Nazwa klienta – pełna nazwa firmy
    • Kwota brutto – kwota faktury
    • Waluta – np. „PLN”
    • Status – „sent” lub „printed” (dla nieopłaconych)
    • uuid – UUID faktury z inFakt
    • Pozostało do zapłaty – kwota do zapłaty
Musisz stworzyć automatyzację, która będzie pobierała faktury z inFakt do Google Sheet. Jeśli nie wiesz jak to zrobić daj znać w komentarzu - dopiszę instrukcję.

Krok 2: Konfiguracja Gmail

  1. W Gmail (lub w innej poczcie z której korzystasz) stwórz nową etykietę np. „Wpłaty-Bank
  2. Stwórz filtr, który automatycznie oznacza maile od banku tą etykietą
  3. Upewnij się, że maile z powiadomieniami o wpłatach trafiają do tej etykiety

Krok 3: Tworzenie workflow w n8n

3.1 Gmail Trigger

  1. Dodaj node Gmail Trigger
  2. Połącz swoje konto Google (OAuth2)
  3. Skonfiguruj:
    • Poll Times: Every Minute
    • Filters → Label IDs: Wybierz swoją etykietę (np. „Wpłaty-Bank”)
    • Read Status: Unread

3.2 Basic LLM Chain (parsowanie maila)

  1. Dodaj node Basic LLM Chain
  2. Połącz z Gmail Trigger
  3. W polu Prompt wklej:
Z poniższego tekstu wyciągnij mi do formatu JSON:

- invoice_number
- client
- paid_date
- amount

Przykłady odpowiedzi:

- invoice_number: 15/06/2025
- client: ESTA CLUSTER
- paid_date: 01.07.2025
- amount: 1230,00 PLN


#Tekst do analizy: 

{{ $json.snippet }}
  1. Zaznacz Has Output Parser

3.3 OpenRouter Chat Model

  1. Dodaj node OpenRouter Chat Model
  2. Połącz z Basic LLM Chain (połączenie AI)
  3. Skonfiguruj credentials OpenRouter

3.4 Code node – formatowanie danych

  1. Dodaj node Code
  2. Nazwij go „payment”
  3. Wklej kod:
// Pobierz odpowiedź z LLM
const llmResponse = $input.first().json.text

// Usuń markdown formatting i sparsuj JSON
const jsonString = llmResponse.replace(/```json\n?/, '').replace(/\n?```/, '');
const parsedJson = JSON.parse(jsonString);

return [{ json: parsedJson }];

3.5 Pobieranie faktur z Google Sheets

  1. Dodaj 2 node’y Google Sheets:
    • Nazwij pierwszy: „get_invoices_printed”
    • Nazwij drugi: „get_invoices_sent”
  2. W obu skonfiguruj:
    • Document ID: ID twojego arkusza
    • Sheet Name: „Infakt”
    • Credentials: Twoje konto Google
  3. W „get_invoices_printed”:
    • Filters: Status = „printed”
  4. W „get_invoices_sent”:
    • Filters: Status = „sent”

3.6 Merge node – łączenie wyników

  1. Dodaj node Merge
  2. Nazwij go „un_paid_invoices”
  3. Połącz oba node’y Google Sheets do niego

3.7 Code node – sprawdzanie czy opłacona

  1. Dodaj node Code
  2. Nazwij go „check_if_paid”
  3. Wklej kod:
// Pobierz wszystkie items z un_paid_invoices
const unpaidInvoices = $('un_paid_invoices').all();
// Pobierz payment data
const paymentData = $('payment').first().json;

console.log('Liczba nieopłaconych faktur:', unpaidInvoices.length);
console.log('Szukana faktura:', paymentData.invoice_number);

// Sprawdź każdy item z nieopłaconych faktur
for (let i = 0; i < unpaidInvoices.length; i++) {
  const invoice = unpaidInvoices[i].json;
  
  console.log(`Sprawdzam fakturę ${i + 1}:`, invoice["Numer faktury"]);
  
  // Dopasuj numer faktury
  if (invoice["Numer faktury"] === paymentData.invoice_number) {
    console.log('Znaleziono dopasowanie!');
    
    return [{
      json: {
        found: true,
        invoice_id: invoice.ID,
        invoice_number: invoice["Numer faktury"],
        client_name: invoice["Nazwa klienta"],
        amount_due: invoice["Pozostało do zapłaty"],
        payment_amount: paymentData.amount,
        payment_date: paymentData.paid_date,
        matched_invoice: invoice
      }
    }];
  }
}

// Jeśli nie znaleziono
return [{
  json: {
    found: false,
    searched_invoice: paymentData.invoice_number,
    searched_client: paymentData.client,
    available_invoices: unpaidInvoices.map(item => item.json["Numer faktury"]),
    message: "Nie znaleziono dopasowanej faktury"
  }
}];

3.8 IF node – decyzja

  1. Dodaj node IF
  2. Skonfiguruj warunek:
    • Left Value: {{ $json.found }}
    • Operation: Boolean → is true

3.9 HTTP Request – oznaczanie w inFakt

  1. Dodaj node HTTP Request
  2. Nazwij go „infakt_mark_as_paid”
  3. Skonfiguruj:
    • Method: POST
    • URL: https://api.infakt.pl/api/v3/async/invoices/{{ $json.matched_invoice.uuid }}/paid.json
    • Headers:
      • Name: X-inFakt-ApiKey
      • Value: [TWÓJ_KLUCZ_API_INFAKT]
    • Body Type: JSON
    • JSON Body:
    json{ "paid_date": "{{ $json.payment_date.split('.').reverse().join('-') }}" }

3.10 Mailgun – powiadomienie

  1. Dodaj node Mailgun
  2. Nazwij go „send_notification”
  3. Skonfiguruj:
    • From Email: noreply@twojafirma.pl
    • To Email: twoj@email.pl
    • Subject: Faktura {{ $('check_if_paid').item.json.matched_invoice['Numer faktury'] }} została opłacona
    • HTML: (możesz użyć szablonu z przykładu)

3.11 Gmail – oznacz jako przeczytany

  1. Dodaj node Gmail
  2. Nazwij go „mark_message_as_read”
  3. Skonfiguruj:
    • Operation: Mark as Read
    • Message ID: {{ $('Gmail Trigger').first().json.id }}

Krok 4: Połączenia między node’ami

Połącz node’y zgodnie ze schematem:

  • Gmail Trigger → Basic LLM Chain
  • Basic LLM Chain → payment
  • payment → oba Google Sheets
  • oba Google Sheets → un_paid_invoices
  • un_paid_invoices → check_if_paid
  • check_if_paid → If
  • If (true) → infakt_mark_as_paid → send_notification → mark_message_as_read
  • If (false) → No Operation

Krok 5: Testowanie

  1. Wyślij sobie testowego maila z banku
  2. Uruchom workflow ręcznie (Execute Workflow)
  3. Sprawdź każdy krok w podglądzie
  4. Upewnij się, że faktura została oznaczona w inFakt

Krok 6: Aktywacja

  1. Kliknij przełącznik Active w prawym górnym rogu
  2. Workflow będzie teraz działał automatycznie co minutę

Wskazówki i rozwiązywanie problemów

Klucz API inFakt

Znajdziesz go w inFakt: Ustawienia → API → Wygeneruj klucz

Format daty

Zwróć uwagę na konwersję daty z polskiego formatu (DD.MM.YYYY) na format API (YYYY-MM-DD)

Debugowanie

  • Używaj console.log() w Code node’ach
  • Sprawdzaj zakładkę „Output” każdego node’a
  • Testuj na pojedynczych mailach

Różne formaty maili bankowych

Dostosuj prompt w LLM Chain do formatu swojego banku

TLDR

Nie masz teraz czasu na pełną lekturę? Poniżej znajdziesz podsumowanie najważniejszych wniosków z całego artykułu.

  • Automatycznie odczytuje maile z banku, wyciąga z nich numer faktury/klienta/kwotę, znajduje pasującą FV w Google Sheets (zaciągniętą z inFakt), oznacza ją przez API jako „opłacona”, wysyła powiadomienie i oznacza mail jako przeczytany.

  • n8n, Gmail (trigger + label), Google Sheets, inFakt API, model LLM przez OpenRouter (do parsowania maili), opcjonalnie Mailgun do powiadomień.

  • AI lepiej znosi różne formaty maili, mniej kodu do utrzymania, tani w użyciu przy tej skali; gdy bank zmieni szablon – aktualizujesz prompt, a nie zestaw regexów.

  • Z osobnej integracji z inFakt → Sheets (arkusz „Infakt” z m.in. numerem FV, statusem, UUID, „Pozostało do zapłaty”). Workflow łączy statusy „sent/printed” jako „nieopłacone”.

  • Po numerze faktury wyciągniętym z maila (LLM). W nodzie check_if_paid porównuje invoice_number z kolumną „Numer faktury”. Możesz dodać walidację kwoty/klienta jako drugi warunek.

  • HTTP POST do:
    /api/v3/async/invoices/{{ uuid }}/paid.json
    z paid_date w formacie YYYY-MM-DD (konwersja z DD.MM.YYYY w Code node). Klucz API podany w nagłówku.

  • Gałąź If (false) nic nie zmienia w inFakt. Możesz dodać alternatywę: wyślij alert/Slack/email z danymi z maila i listą dostępnych numerów do ręcznego sprawdzenia.

  • Gmail Trigger co minutę na etykiecie (np. „Wpłaty-Bank”). Klucze API trzymaj w Credentials n8n. Ogranicz źródła: filtruj po nadawcy, etykiecie i rozważ whitelistę domen banku.

  • Koszt LLM przez OpenRouter jest minimalny przy krótkich promptach. Reszta to wywołania API i odczyt z Sheets. Działa w sekundach; realne opóźnienie to polling Gmail (do 1 min).

  • Tak — regexami. Ale będziesz częściej aktualizować wzorce przy zmianach formatów maili. Hybryda (AI + proste regexy dla sanity-check, np. numeru FV i kwoty) jest często najlepsza.

Marek Golan

CEO Dogtronic, Prezes ESTA Cluster, dumny opiekun Borysa. Ma prawie 20 lat doświadczenia w branży IT zarówno jako programista jak i właściciel firm technologicznych.

Przekształć
swoje pomysły
w rzeczywistość

Skontaktuj się z nami i pozwól nam sprawdzić jak możemy Ci pomóc.

Najciekawsze treści na Blogu

Zobacz wszystkie

Zautomatyzuj swój dział sprzedaży – proces obsługi klienta w N8N

Formularz kontaktowy – poces automatyzacji.

Czy Twój zespół spędza cenne godziny na ręcznym kopiowaniu danych z formularzy kontaktowych do CRM? Czy chciałbyś mieć system, który nie tylko odbiera zapytania od klientów, ale także je analizuje, przygotowuje wstępną ofertę i informuje Cię o wszystkim np na Slacku?

Jeśli tak, to ten poradnik jest dla Ciebie.

Pokażemy Ci, jak krok po kroku zbudować w N8N w pełni zautomatyzowany proces, który wesprze Twój dział sprzedaży. Stworzymy "cyfrowego asystenta", który pracuje 24/7, nie popełnia błędów i pozwala Twojemu zespołowi skupić się na tym, co najważniejsze – na rozmowie z klientami.

Co zbudujemy?

Nasz system będzie:

  1. Odbierał zapytania z formularza kontaktowego (WordPress) na stronie oraz z dedykowanej skrzynki e-mail.
  2. Analizował treść zapytania za pomocą AI (chatGPT), tworząc zwięzły tytuł i wstępną ofertę.
  3. Automatycznie tworzył kontakt i szansę sprzedaży w Pipedrive CRM.
  4. Wysyłał natychmiastowe powiadomienie na Slacka z linkiem do CRM.
  5. Wysyłał do klienta e-mail z potwierdzeniem otrzymania wiadomości.

Czego potrzebujesz?

  • Konto N8N (w chmurze lub na własnym serwerze).
  • Dostęp do API OpenAI.
  • Konto w Pipedrive (lub innym CRM, proces można łatwo dostosować).
  • Konto Slack. (opcjonalnie)
  • Konto w serwisie do wysyłki e-maili (np. Mailgun, SendGrid)

Konfiguracja WordPress

Większość z nas posiada strony internetowe oparte o system CMS WordPress. Formularz kontaktowy w większości przypadku będzie oparty o wtyczkę Contact Form 7. Gdy potencjalny klient wyślę do nas zapytanie przez tę wtyczkę musimy przesłać dane zebranę przez formularz do N8N. Do tego posłuży nam wtyczka CF7 to Webhook.

Zainstaluj tę wtyczkę i skonfiguruj.

Contact Form w N8N.
Webhook URL – tutaj będziesz musiał podać adres swojego webhooka.
Poznasz ten adres w kolejnych krokach.

Przejdź do ustawień zaawansowanych wtyczki. Ustaw metodę wysyłki danych na POST i wklej poniższy kod do zakładki Body.

{
  "message": {
    "content": "New message!",
    "sender": {
      "name": "[your-name]",
      "email": "[your-email]",
      "phone": "[your-phone]"
    },
    "body": "[your-message]",
    "dataProcessingAccepted": "[acceptance-data-processing]"
  }

}

W razie potrzeby dostosuj nazwy pól [your-name] itd do swojej struktury.

Contact Form w N8N.
Sprawdź jakich zmiennych używasz w swoim formularzy i dostosuj je do powyższej struktury.

Odbieranie wiadomości

Stwórz nowy proces w N8N i zacznij krok po kroku go budować.

Zaczniemy od zbudowania części odpowiadającej za odbieranie danych z formularza kontaktowego oraz skrzynki mailowej.

Proces automatyzacji w N8N.
W swoim procesie możesz użyć tylko jednego źródła danych. Np. skrzynka mailowa.

Odbiór wiadomości z formularza kontaktowego

1. Dodaj node typu Webhook

  • Metoda: POST
  • Ścieżka: np. /webhook/contact-form-7

W Contact Form 7 w WordPressie ustaw url wysyłki do Twojego webhooka:

https://twoja-domena.pl/webhook/contact-form-7

2. Dodaj node typu Edit Fields.

Edycja pliku.
Nazwij node Parse From Webhook

Odbiór wiadomości ze skrzynki mailowej

1. Trigger na e-mail (IMAP)

Dodaj node Email Trigger (IMAP)

  • Host, port, user, pass (Twoja skrzynka, np. sales@twoja-domena.pl)
  • Oznacz, by wywoływał się na każde nowe wiadomości.
Wyzwalacz wiadomości email.
W polu Action wybierz „Mark as Read”

2. UTF-8 support

Dodaj node typu Code. Ten node jest przydatny w przypadku problemów z polskimi znakami w odbieranych mailach.

Ten blok powinien wyglądać tak:

UTF-8.
Nazwij node UTF-8 support

Kod do skopiowania:

const text = $input.first().json.textPlain// Zakładając, że treść wiadomości jest w polu 'text'
const correctedText = Buffer.from(text, 'binary').toString('utf8');
$input.item.json.text = correctedText;
return $input.item;

3. Parse From Mail

Przetwórz dane odebrane z wiadomości email. Dodaj node Set.

Zrzut ekranu.
Nazwij node Parse From Mail

Zmienne do skopiowania:

  • name: {{ $json.from.replace(/<.*>/, '').trim() }}
  • subject: {{ $json.subject }}
  • email: {{ $json.from.match(/<([^>]+)>/)[1] }}
  • body: {{ $json.text }}
  • phone: 999 999 99

Scalanie danych

  • Wstaw node Merge (tryb Append)
  • Połącz wyjścia z Webhooka i z Email Triggera
  • Efekt: jednolity obiekt:
Merge plików.
Nazwij node Merge Input

Generowanie tytułu i oferty przez GPT-4

Dodaj Node OpenAI.

Node – Chat GPT.
Dodaj węzeł OpenAI i połącz go za węzłem Merge. Nazwij node GPT processing
  • Wybierz model, np. gpt-4o.
  • W polu Messages, w roli Assistant, wklej precyzyjnie przygotowany prompt.

To najważniejsza część – mówisz AI, co dokładnie ma zrobić. Oto przykład z naszego procesu:

Komtekst: obsługujemy zapytanie od klienta. Musimy stworzyć dla niego ofertę. Zrób po polsku podsumowanie wiadomości w 1 zdaniu. Niech to będzie tytuł szansy sprzedaży do CRM. Zapytanie od potencjalnego klienta:
{{ $json.body }}


Przygotuj ofertę na wykonanie zlecenia o której pyta klient. Staraj się podać przedział cenowy. Podpisz się jako Marek Golan firma Dogtronic Sp. z o.o .Odpowiedź rozbij na 2 sekcje:
- tytuł szansy sprzedaży (zmienna "title")
- oferta (zmienna "offer") bez kolejnych sekcji

Oczywiście powyższy prompt przygotuje nam ofertę na dużym poziomie ogólności. Nie należy się nim sugerować. To co nam napisze AI możemy traktować jako podpowiedź w jaki sposób odpisać osobie, która zadała nam pytanie. W kolejnym poradniku napiszemy jak dodać wiedzę o naszej formie / ofercie do modelu AI by odpowiedzi były bardziej wartościowe.

GPT node.
Upewnij się, że zaznaczyłeś opcję JSON Output, aby otrzymać dane w ustrukturyzowanej formie.

Scalanie danych

Musisz ponownie scalić dane odebrane z 2 źródeł.

Scalanie danych.
Dodaj 2 nowe node Set

1. Dodaj node Set nazwij go GPT form.

GPT form.
Zaznacz mode Manual Mapping

Zmienne do skopiowania:

  • title: {{ $json.message.content.title }}
  • name: {{ $('Parse From Webhook').item.json.name }}
  • email: {{ $('Parse From Webhook').item.json.email }}
  • body: {{ $('Parse From Webhook').item.json.body }}
  • offer: {{ $('GPT processing').item.json.message.content.offer }}
  • phone: {{ $('Parse From Webhook').item.json.phone }}

2. Dodaj node Set nazwij go GPT Mail.

GPT mail.
Zaznacz mode Manual Mapping

Zmienne do skopiowania:

  • name: {{ $('Merge Input').item.json.name }}
  • title: {{ $('GPT processing').item.json.message.content.title }}
  • offer: {{ $('GPT processing').item.json.message.content.offer }}
  • email: {{ $('Merge Input').item.json.email }}
  • body: {{ $('Parse From Mail').item.json.body.replace(/\r?\n/g, '\n') }}

Integracja z CRM PipeDrive

Czas automatycznie dodać wszystko do naszego systemu CRM.

Ścieżka przesyły danych.
Połącz swoje node w powyższy sposób.

Wyszukaj Osobę: Dodaj węzeł Pipedrive i wybierz operację Person -> Search. W polu Term wstaw e-mail klienta z poprzednich kroków. To zapobiegnie tworzeniu duplikatów.

Pipe Drive – przeszukiwanie danych.
Nazwij node Search Person in Pipedrive

Jaki url podaj:

https://api.pipedrive.com/v1/itemSearch?term={{$json.email}}&field_key=email&exact_match=true&item_types=person

Dodaj node Code

Pipe Drive – sprawdzanie poprawności danych.
Nazwij node Check if person is in Pipedrive and output the ID

Wklej poniższy kod:

const inputItems = $input.all();

// Check if there are items and that the items array is not empty
const hasItems = inputItems.length > 0 && inputItems[0]?.json?.data?.items.length > 0;

// Retrieve the ID of the first item if available
const firstItemId = hasItems ? inputItems[0].json.data.items[0].item.id : null;

return { hasItems, firstItemId };

Sprawdź, czy Istnieje: Dodaj węzeł IF. Ustaw warunek, który sprawdzi, czy poprzedni krok znalazł jakieś wyniki.

Pipe Drive – funkcja if.
Nazwij node IF Person exists in Pipedrive

Dwie Ścieżki:

Pipe drive – proces automatyzacji.
Sprawdzamy czy osoba jest już w bazie PipeDrive

Ścieżka „Fałsz” (Brak osoby w CRM):
Połącz ją z nowym węzłem Pipedrive ustawionym na Person -> Create. Wypełnij pola name, email, phone, aby stworzyć nowy kontakt.

Pipe drive – funkcja false.
Nazwij node PipeDrive

Zmienne do skopiowania:

  • Name: {{ $('Merge Input').item.json.name }}
  • Email: {{ $('Merge Input').item.json.email }}
  • Phone: {{ $('Merge Input').item.json.phone }}

Ścieżka „Prawda”: Zostaw ją pustą – idziemy dalej.

Dodaj node Code w którym zadbamy o Person ID.

Pipe drive – funkcja true.
Nazwij node New Person ID

Wklej poniższy kod:

// Check whether the value from the previous node is available
let personId;

// Check whether the value of 'IF Person exists in Pipedrive' node is present
if ($('IF Person exists in Pipedrive').item.json.firstItemId) {
    personId = $('IF Person exists in Pipedrive').item.json.firstItemId;
} 
// If the above value is not available, check whether a value of '$('Pipedrive').item.json.id' is available
else if ($('Pipedrive').item.json.id) {
    personId = $('Pipedrive').item.json.id;
}

// If a value was found for personId, it is output as a new variable
if (personId) {
    return { person_id: personId };
} else {
    return { error: 'Keine Person-ID gefunden' };
}

Utwórz Szansę Sprzedaży (Lead):
Połącz obie ścieżki z węzłem Pipedrive ustawionym na Lead -> Create.

Pipe drive – tworzenie leadów.
Nazwij node Crete lead

Zmienne do skopiowania:

  • title: {{ $('GPT processing').item.json.message.content.title }}
  • Person ID: {{ $json.person_id }}

Powiąż leada z osobą, używając ID z kroku wyszukiwania lub tworzenia.

Dodaj Notatkę: Na końcu dodaj jeszcze jeden węzeł Pipedrive (Note -> Create). W treści notatki

Umieść oryginalne zapytanie klienta oraz ofertę od AI. To da Twojemu zespołowi pełen kontekst.

Pipe drive – tworzenie notatek.
Nazwij node Create Node

Zmienne:

  • Zapytanie: {{ $('Merge Input').item.json.body }}
  • Oferta (AI): {{ $('GPT processing').first().json.message.content.offer }}

Efekt:

Przykład wpisu w CRM.
Zapytanie zostaje przekształcone w lead i trafia do PipeDrive.

Powiadomienia

Slack

W tym kroku wyślemy wiadomość na Slacka oraz na maila do klienta oraz naszego zespołu.

Wysłanie odpowiedzi do klienta.
W naszym przykładzie używamy serwisu MailGun do wysyłki maili.

Skonfiguruj node Slack. Pamiętaj o autoryzacji.

Wysłanie informacji na Slacku.
Channel ID pobierzesz z właściwości kanału na Slacku.

Skopiuj poniższy kod do pola Blocks

{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": "Nowa wiadomość z Dogtronic.io :email:",
				"emoji": true
			}
		},
		{
			"type": "context",
			"elements": [
				{
					"type": "mrkdwn",
					"text": " {{ $('Merge Input').item.json.name }} {{ $('Merge Input').item.json.email }}"
				}
			]
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "*Analiza (AI):* {{ $('GPT processing').item.json.message.content.title }} \r\n "
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "*Klient:* \r\n {{ $('Merge Input').item.json.body.replace(/\r?\n/g, '\\n')  }}"
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "*Oferta (AI):* \r\n {{ $('GPT processing').item.json.message.content.offer.replace(/\n/g, '\\n'); }}"
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Zaplanuj działanie w CRM"
			},
			"accessory": {
				"type": "button",
				"text": {
					"type": "plain_text",
					"text": "PipeDrive",
					"emoji": true
				},
				"value": "click_me_123",
				"url": "https://dogtronic.pipedrive.com/deal/{{ $json.deal_id }}",
				"action_id": "button-action"
			}
		}
	]
} 

Efekt:

Przykład wiadomości na Slack.
Powiadomienie o nowym potencjalnym klienci.
Odpowiedź AI jest wyłącznie do naszego wglądu.

Email

Zadbajmy o profesjonalny wizerunek i spokój klienta.

Dodaj węzeł do wysyłki e-maili, np. Mailgun.

W polu To Email wstaw adres e-mail klienta.

Napisz treść maila z podziękowaniem za kontakt i potwierdzeniem otrzymania danych. Możesz nawet wkleić wiadomość, którą wysłał, aby miał pewność, że wszystko dotarło poprawnie.

Wysłanie emaila z notyfikacją.
Nazwij node Send mail to client

Przykładowa treść wiadomości do klienta:

Szanowni Państwo,

Dziękujemy za przesłanie wiadomości za pośrednictwem naszego formularza kontaktowego. Potwierdzamy otrzymanie Państwa zapytania i poinformujemy, że skontaktujemy się z Państwem najszybciej, jak to możliwe, aby omówić szczegóły.

Dla przypomnienia, poniżej znajdują się dane, które otrzymaliśmy:

Imię i nazwisko: {{ $('Merge Input').item.json.name }}

Adres e-mail: {{ $('Merge Input').item.json.email }}

Treść wiadomości:
{{ $('Merge Input').item.json.body.replace(/\n/g, '
') }}

Jeśli mają Państwo dodatkowe pytania lub chcą uzupełnić swoje zapytanie, prosimy o odpowiedź na tę wiadomość.

Z poważaniem,
Zespół Dogtronic

Efekt

Przykład powiadomienia email.
Mail, który otrzymuje nasz zespół oraz klient.

Aktywacja

Połącz wszystkie węzły zgodnie z opisaną logiką, upewnij się, że wszystkie ścieżki są połączone, a na koniec kliknij przełącznik Active w prawym górnym rogu.

Gotowe! Właśnie zbudowałeś potężną machinę, która oszczędzi Ci dziesiątki godzin i sprawi, że żaden potencjalny klient nie zostanie pominięty.


Co dalej? To dopiero początek! Możesz rozbudować ten proces o kolejne kroki:

  • Automatyczne przypisywanie handlowców do leadów.
  • Wysyłanie spersonalizowanych follow-upów po kilku dniach.
  • Analizę sentymentu wiadomości od klienta.

Automatyzacja to nie przyszłość, to teraźniejszość. Eksperymentuj, ucz się i daj znać w komentarzach, jakie procesy Ty zautomatyzowałeś w swojej firmie 🙂

Chcesz przetestować działanie tego procesu? Napisz na sales@dogtronic.io lub wypełnij nasz formularz kontaktowy.

TLDR

Nie masz teraz czasu na pełną lekturę? Poniżej znajdziesz podsumowanie najważniejszych wniosków z całego artykułu.

  • Zbiera zapytania z formularza (WordPress/CF7 → Webhook) lub skrzynki e-mail (IMAP), streszcza i tworzy wstępną ofertę (AI), zakłada kontakt i leada w Pipedrive, wysyła powiadomienie na Slacka i potwierdzenie do klienta.

  • n8n, WordPress (CF7 + CF7 to Webhook) lub IMAP, OpenAI (API), Pipedrive, Slack (opcjonalnie) oraz usługa e-mail (Mailgun/SendGrid).

  • CF7 to Webhook wysyła JSON metodą POST na Twój Webhook w n8n. Wtyczce ustaw URL, metodę POST i body z mapowaniem pól (name, email, phone, body).

  • Użyj Email Trigger (IMAP) w n8n. Node „UTF-8 support” poprawia polskie znaki. „Parse From Mail” wyciąga: name, email, subject, body. Potem Merge z danymi z Webhooka (jeśli używasz obu źródeł).

  • Node OpenAI tworzy tytuł szansy („title”) i szkic oferty („offer”) na bazie treści wiadomości. Dzięki temu handlowiec startuje od gotowego draftu, nie od zera.

  • Najpierw Search Person po e-mailu (unikasz duplikatów). Jeśli brak — Create Person. Następnie Create Lead z tytułem od AI i Create Note z treścią zapytania + ofertą AI.

  • Slack dostaje blokową wiadomość z podsumowaniem (tytuł AI, treść klienta) i przyciskiem do leada w Pipedrive. Klient dostaje mail potwierdzający odbiór z kopią jego wiadomości.

  • Trzymaj klucze w Credentials n8n. Dbaj o formaty znaków (UTF-8), poprawne mapowanie pól z CF7 i walidację e-maili. Loguj console.log() w Code-node’ach i testuj każdy krok.

  • Nie. Możesz użyć jednego źródła. Jeśli oba są włączone, Merge spina je w jeden ujednolicony obiekt wejściowy.

  • Auto-assign handlowców, follow-upy po X dniach, analiza sentymentu, scoring leadów, załączniki do Pipedrive, wzbogacenie AI o wiedzę o ofercie (RAG) i alerty o błędach na Slacka.

Marek Golan

CEO Dogtronic, Prezes ESTA Cluster, dumny opiekun Borysa. Ma prawie 20 lat doświadczenia w branży IT zarówno jako programista jak i właściciel firm technologicznych.

Przekształć
swoje pomysły
w rzeczywistość

Skontaktuj się z nami i pozwól nam sprawdzić jak możemy Ci pomóc.

Najciekawsze treści na Blogu

Zobacz wszystkie

Automatyzacja kopii zapasowej repozytorium na GitHubie

Czasami zdarza się tak, że nad kodem pracuje więcej niż jeden podwykonawca. Przykładowo, jedna firma – Wykonawca X – odpowiada za pisanie kodu aplikacji, a druga – Wykonawca Y – ma regularnie odpowiadać za kontrolę jego jakości.

W takiej sytuacji optymalnym rozwiązaniem jest regularne przesyłanie kopii kodu napisanego przez Wykonawcę X do Wykonawcy Y.

Wbrew pozorom takie drobne, regularne czynności potrafią być całkiem uciążliwe. Jest to więc modelowy proces, który powinno się automatyzować, aby zaoszczędzić czas, energię, a w konsekwencji pieniądze.

W poniższym wpisie umieściliśmy krótką instrukcję, dzięki której można zaoszczędzić trochę czasu na regularnym tworzeniu backupów.

Instrukcja

Poniższa instrukcja wyjaśnia jak przygotować skrypt automatyzujący zrobienie kopii zapasowej kodu między dwoma repozytoriami na GitHubie.

1. Utwórz nowe repozytorium 

Repozytorium z domyślną gałęzią może być prywatne.

Ustawienia nowego repozytorium na Github.

Repozytorium powinno zawierać pusty plik np. readme.md.

Nowe repozytorium Github.

2. Wygeneruj PAT (Personal Access Token)

Token PAT wygenerujesz w zakładce Developer settings na swoim koncie GitHub.

Następnie musisz wybrać okres ważności tokenu. Można wybrać opcję, aby PAT nie wygasał, ale nie jest to najbezpieczniejsze rozwiązanie, więc go nie zalecamy.

WAŻNE: token powinien posiadać uprawnienia repo.

Generowanie Personal Access Token w Github.

3. W repozytorium źródłowym dodaj nowy sekret z wygenerowanym tokenem

Możesz to zrobić na swoim koncie GitHub w zakładce: Security → Secret and variables → Actions.

Dla ułatwienia możesz skorzystać z poniższego schematu adresu URL (podmień ’owner’ i ’repo’).

https://github.com/owner/repo/settings/secrets/actions
Dodawanie sekretu w repozytorium Github.

4. Skopiuj poniższy kod źródłowy

Poniższy kod źródłowy umieść w postaci pliku np. backup.yml w katalogu .github/workflows na docelowej gałęzi w źródłowym repozytorium.

# Steps to make this workflow work
# 1. Create new repository with any default branch (it can just contain dummy readme.md file)
# 2. Generate PAT with repo priveleges
# 3. Add PAT as github secret in source repository
# 4. Add this workflow to source repository
# 5. Change whatever variables you want to change in this workflow (target repo with default branch and source branch are required)
# Disclaimer: This workflow doesn't copy any workflow files from source repository to target repository. It only copies source code.

name: Push code to another Github repository

on:
  push:
    branches:
      - <source-repository-target-branch> # swap this for source repository target branch name

jobs:
  push:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout source repository
      uses: actions/checkout@v4
      with:
        path: src
        ref: <source-repository-target-branch> # swap this for source repository target branch name
        persist-credentials: false
    - name: Get current timestamp
      id: date
      run: echo "::set-output name=timestamp::$(date +'%Y%m%d%H%M%S')"
    - name: Set target branch name
      run: echo "TARGET_BRANCH=master-${{ steps.date.outputs.timestamp }}" >> $GITHUB_ENV # rename new branch name to match your naming convention
    - name: Checkout target repository
      uses: actions/checkout@v4
      with:
        path: target
        ref: <target-repository-target-branch> # swap this for target repository default branch name
        repository: <owner>/<repository> # swap this for target repository
        token: ${{ secrets.TARGET_REPO_TOKEN }} # remember to add github secret in source repository with github PAT that has repo priveleges
        fetch-depth: 0
        persist-credentials: true
    - name: Push to Target Repository
      working-directory: ./target
      run: |
        git checkout -b ${{ env.TARGET_BRANCH }}
        rm -rf ./*
        cp -r ../src/* .
        git config --local user.name "Dummy User"
        git config --local user.email "dummy@email.com"
        git add .
        git commit -m 'Create new backup'
        git push -u origin ${{ env.TARGET_BRANCH }}

5. Podmień zmienne w kodzie skryptu

Podmień wszystkie zmienne wyróżnione ’<>’ w kodzie skryptu, tak aby odpowiadały nazwom Twojego repozytorium oraz gałęzi.

I to by było na tyle

Jeśli wszystkie kroki zostały wykonane poprawnie, to skrypt będzie uruchamiany za każdym razem kiedy na gałąź docelową zostanie wypchnięty kod.

Podgląd poprawnie utworzonego repozytorium

Aby upewnić się, że wszytko działa tak jak powinno, sprawdź zgodność swojego repozytorium z podglądem na poniższych zrzutach ekranu:

1. Docelowa gałąź na repozytorium źródłowym:

Docelowa gałąź na repozytorium źródłowym Github.

2. Widok Actions w repozytorium źródłowym:

Widok Action w repozytorium źródłowym Github.

3. Nowo utworzona gałąź w docelowym repozytorium:

Nowo utworzona gałąź w docelowym repozytorium Github.

TLDR

Nie masz teraz czasu na pełną lekturę? Poniżej znajdziesz podsumowanie najważniejszych wniosków z całego artykułu.

  • Automatycznie kopiuje kod z repo źródłowego do repo docelowego na GitHubie, tworząc nową gałąź w repo docelowym z kopią bieżącego stanu (backup dla kontroli jakości u drugiego wykonawcy).

  • Repo docelowe (może być prywatne, z pustym plikiem np. readme.md), PAT z uprawnieniami repo, sekret w repo źródłowym (TARGET_REPO_TOKEN), plik workflow w .github/workflows/backup.yml.

  • W repo źródłowym: Settings → Security → Secrets and variables → Actions
    (bezpośredni schemat URL: https://github.com/<owner>/<repo>/settings/secrets/actions).

  • Na push do wskazanej gałęzi w repo źródłowym (on.push.branches: <source-repository-target-branch>). Po puszu automatycznie tworzy backup w repo docelowym.

  • Wyłącznie kod źródłowy (katalogi/pliki z repo źródłowego). Nie kopiuje plików workflow z .github/workflows.

  • Automatycznie: master-YYYYMMDDHHMMSS (np. master-20250701123045). Możesz zmienić wzorzec w kroku „Set target branch name”.

    • <source-repository-target-branch> – gałąź w repo źródłowym, która wyzwala kopiowanie.
    • <target-repository-target-branch> – domyślna gałąź repo docelowego.
    • <owner>/<repository> – pełna ścieżka repo docelowego.

    Upewnij się też, że sekret nazywa się jak w YAML: TARGET_REPO_TOKEN.

  • Checkout źródła → ustawia znacznik czasu → tworzy nazwę docelowej gałęzi → checkout repo docelowego (z tokenem) → tworzy nową gałąź → czyści katalog → kopiuje pliki ze src → git add/commit/push.

    • 403/401: zły PAT, brak scope repo, sekret w złym repo lub złą nazwą.
    • Gałąź nie istnieje: sprawdź nazwy gałęzi źródłowej/docelowej.
    • Puste commity: nic się nie zmieniło – sprawdź, czy cp -r ../src/* . faktycznie kopiuje (ukryte pliki .gitignore itp.).
    • Konflikt historii: workflow nadpisuje zawartość — to zamierzony backup do nowych gałęzi.
  • Ustaw okres ważności PAT (nie „never”), trzymaj go wyłącznie w Secrets, rozważ ograniczenie uprawnień do minimum. Opcjonalnie dodaj workflow_dispatch/schedule dla ręcznego lub cyklicznego backupu.

Karol Ścibior

Specjalista TypeScript | Node | React. Full-Stack Developer, dla którego nie ma projektów niewykonalnych.

Przekształć
swoje pomysły
w rzeczywistość

Skontaktuj się z nami i pozwól nam sprawdzić jak możemy Ci pomóc.

Najciekawsze treści na Blogu

Zobacz wszystkie