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:
- Konto w n8n (możesz użyć wersji cloud lub self-hosted).
- Arkusz Google Sheets z danymi o klientach i usługach.
- Konto w Infakt z dostępem do API (potrzebny klucz API).
- Konto w Mailgun (lub inna usługa do wysyłania maili) (potrzebny klucz API).
- 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:
- Usługi – tu przechowujemy informacje o klientach i ich usługach.
- 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

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:

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 }}
.
- ID:
- 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:

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:
- Upewnij się, że w arkuszu „Usługi” masz co najmniej jeden wiersz z datą ostatniej faktury, która wymaga wystawienia nowej faktury.
- Uruchom workflow manualnie, aby sprawdzić, czy faktura jest poprawnie tworzona i wysyłana.
- Sprawdź logi w n8n, aby upewnić się, że nie ma błędów.
- Zweryfikuj, czy arkusz „Historia_faktur” został zaktualizowany.
- 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
- Dokumentacja n8n: https://docs.n8n.io/.
- Dokumentacja Infakt API: https://www.infakt.pl/developers.
- Dokumentacja Mailgun: https://documentation.mailgun.com/.
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 🐶.