Automationn8nInvoicingFinance OpsSmall Business

How to Automate Your Invoicing Process: Step-by-Step n8n Guide (2026)

Adrijan Omičević··16 min read
Share

# What You’ll Build (and Who This Is For)#

This guide shows how to automate invoicing process end-to-end using n8n: generate invoices, send them, schedule reminders, and reconcile payments so you can stop chasing spreadsheets and missed emails.

It’s for service businesses, agencies, and B2B teams that bill monthly/weekly, especially when you have recurring clients and predictable billing cycles.

If you’re not sure automation is worth it yet, read 5 signs your business needs automation.

# Outcome: The Automated Invoicing Pipeline#

By the end you’ll have a workflow that:

  • Pulls billable items (time entries, subscriptions, retainers, usage) from your source.
  • Creates an invoice number, totals, and due date reliably.
  • Generates a PDF invoice from a template.
  • Sends the invoice email with payment instructions or a payment link.
  • Sends reminders on a schedule until paid.
  • Reconciles payments (Stripe/PayPal/bank export) and stops reminders instantly.
  • Writes an audit trail (invoice log + event timeline) for accounting.

# Prerequisites (Keep It Simple)#

You can swap tools based on your stack; the workflow structure stays the same.

  • n8n (cloud or self-hosted)
  • Data source for billables:
    • Airtable / Google Sheets / Notion / HubSpot / Pipedrive / PostgreSQL
  • Email delivery:
    • SMTP, Gmail, Microsoft 365, SendGrid, Postmark, etc.
  • PDF generation method:
    • PDFMonkey / DocRaptor / APITemplate.io, or your own HTML→PDF endpoint
  • Payment data source for reconciliation (choose one):
    • Stripe / PayPal
    • Bank CSV export
    • Accounting system API (Xero/QuickBooks) if you already use one

If you want this built and maintained as a production-grade automation, see our automation services: Samioda Automation.

To prevent duplicates and make reconciliation reliable, keep two tables/collections:

  1. 1

    Customers

    • customerId
    • name, email
    • billingAddress, vatId (optional)
    • paymentTermsDays (e.g., 14)
    • currency
    • active flag
  2. 2

    Invoices

    • invoiceId (internal UUID)
    • invoiceNumber (human-readable, sequential or period-based)
    • customerId
    • periodStart, periodEnd
    • issueDate, dueDate
    • lineItems (array)
    • subtotal, tax, total
    • status (draft, sent, paid, void, overdue)
    • pdfUrl / pdfFileId
    • sentAt, paidAt
    • externalPaymentRef (Stripe charge ID, bank ref, etc.)
    • Idempotency key (critical): key = customerId + period + total + version
  3. 3

    Invoice Events (optional but recommended)

    • invoiceId
    • type (generated, sent, reminder_1, reminder_2, paid_detected, reconciled)
    • timestamp
    • payload (email message ID, payment ID, etc.)

This structure is what makes automation safe: you always check before you create/send.

⚠️ Warning: Idempotency is critical in invoicing automation. A single workflow retry due to a network glitch or crashed step can generate duplicate invoices, overcharge customers, and create nightmares for reconciliation. Always generate an idempotency key (customerId + period + total), check it before creating an invoice, and use database-level unique constraints to prevent duplicates. There is no "undo" in billing systems—prevent duplicates at the source.

# Workflow Diagram (Text Description)#

You will build two n8n workflows:

Workflow A — Generate + Send Invoices (Scheduled)#

Diagram description:

  1. 1
    Cron (monthly/weekly)
  2. 2
    Fetch billables (DB/Sheets/CRM)
  3. 3
    For each customer (Split in Batches)
  4. 4
    Compute invoice + idempotency key (Function)
  5. 5
    Check existing invoice (DB lookup)
    • If exists → Skip
    • If not → continue
  6. 6
    Render invoice HTML (Template in Function)
  7. 7
    Generate PDF (HTTP Request to PDF service)
  8. 8
    Persist invoice (DB insert/update)
  9. 9
    Send invoice email (Email node)
  10. 10
    Log event (DB insert)

Workflow B — Reminders + Reconciliation (Daily)#

Diagram description:

  1. 1
    Cron (daily)
  2. 2
    Fetch open invoices (status = sent/overdue)
  3. 3
    Fetch payments (Stripe/PayPal/bank export)
  4. 4
    Match payments to invoices (Function)
    • If matched → mark paid + log
  5. 5
    Find invoices needing reminders (due date logic)
  6. 6
    Send reminders (Email node)
  7. 7
    Log event (DB insert)

Keeping them separate prevents a reminder run from accidentally generating invoices, and makes debugging easier.

💡 Tip: Separate workflows for invoice generation and reminders isn't just a best practice—it's a safety barrier. If Workflow A (generate + send) crashes halfway through, Workflow B (reconcile + remind) won't accidentally spam reminders to customers who never received invoices in the first place. You can also toggle each workflow independently for maintenance or testing without disrupting the other.

# Step 1: Define Your Invoice Numbering and Period Rules#

Choose one predictable invoice number pattern and never change it mid-year.

Common patterns:

PatternExampleBest forNotes
Sequential000142Stable volumeRequires careful locking to avoid collisions
Year + sequence2026-0012Accounting clarityReset sequence yearly
Customer + periodACME-2026-02Recurring clientsEasy matching, not always compliant everywhere

Practical recommendation: YYYY-#### (e.g., 2026-0012) with a database sequence, or a “last invoice number” record.

Also define billing period rules:

  • Monthly invoices: periodStart = first day of last month, periodEnd = last day of last month
  • Retainers: issue on the 1st for the same month, due in 7/14 days
  • Usage billing: invoice previous month’s usage

# Step 2: Create the Invoice Template (HTML)#

Most PDF generators accept HTML. Keep it simple and consistent.

Example (minimal) HTML template (store it in an n8n Function node as a string, or load from your repo via HTTP):

HTML
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <style>
      body { font-family: Arial, sans-serif; font-size: 12px; color: #111; }
      .header { display:flex; justify-content:space-between; margin-bottom: 24px; }
      .title { font-size: 20px; font-weight: 700; }
      table { width: 100%; border-collapse: collapse; margin-top: 16px; }
      th, td { border-bottom: 1px solid #eee; padding: 8px; text-align: left; }
      .right { text-align:right; }
      .totals { margin-top: 12px; width: 40%; margin-left: auto; }
    </style>
  </head>
  <body>
    <div class="header">
      <div>
        <div class="title">Invoice {{invoiceNumber}}</div>
        <div>Issue date: {{issueDate}}</div>
        <div>Due date: {{dueDate}}</div>
      </div>
      <div>
        <strong>{{companyName}}</strong><br/>
        {{companyAddress}}<br/>
        VAT: {{companyVat}}
      </div>
    </div>

    <div>
      <strong>Billed to</strong><br/>
      {{customerName}}<br/>
      {{customerAddress}}<br/>
      VAT: {{customerVat}}
    </div>

    <table>
      <thead>
        <tr>
          <th>Description</th>
          <th class="right">Qty</th>
          <th class="right">Unit</th>
          <th class="right">Total</th>
        </tr>
      </thead>
      <tbody>
        {{lineItems}}
      </tbody>
    </table>

    <table class="totals">
      <tr><td>Subtotal</td><td class="right">{{subtotal}}</td></tr>
      <tr><td>Tax</td><td class="right">{{tax}}</td></tr>
      <tr><td><strong>Total</strong></td><td class="right"><strong>{{total}}</strong></td></tr>
    </table>

    <p style="margin-top:16px;">
      Payment: {{paymentInstructions}}
    </p>
  </body>
</html>

You’ll replace {{...}} with real values inside n8n.

# Step 3: Workflow A in n8n — Generate and Send Invoices#

3.1 Create the Trigger (Cron)#

Use Cron to run on your billing schedule:

  • Monthly: 1st day of month at 08:00
  • Weekly: Monday at 08:00

Tip: run in the morning in your primary timezone, and store timezone explicitly.

3.2 Fetch Billable Items#

Use one node depending on your system:

  • Postgres node: query time entries
  • Google Sheets node: read rows
  • HTTP Request node: call your CRM/billing API

You want data grouped per customer and period.

Example SQL (Postgres) to aggregate last month:

SQL
SELECT
  customer_id,
  date_trunc('month', now() - interval '1 month') AS period_start,
  (date_trunc('month', now()) - interval '1 day')::date AS period_end,
  json_agg(json_build_object(
    'description', description,
    'qty', hours,
    'unitPrice', hourly_rate
  )) AS line_items
FROM time_entries
WHERE started_at >= date_trunc('month', now() - interval '1 month')
  AND started_at < date_trunc('month', now())
GROUP BY customer_id;

3.3 Split per Customer (Split in Batches)#

Add Split In Batches so each invoice is processed independently. This makes failures isolated and retryable.

3.4 Compute Totals + Idempotency Key (Function)#

Use a Function node to calculate totals and create an idempotency key.

JavaScript
function money(n) {
  return Math.round((n + Number.EPSILON) * 100) / 100;
}

const customerId = $json.customer_id;
const periodStart = $json.period_start;
const periodEnd = $json.period_end;
const items = $json.line_items;

let subtotal = 0;
const normalizedItems = items.map((it) => {
  const qty = Number(it.qty || 0);
  const unitPrice = Number(it.unitPrice || 0);
  const total = money(qty * unitPrice);
  subtotal += total;
  return { ...it, qty, unitPrice, total };
});

subtotal = money(subtotal);
const taxRate = 0; // set your VAT logic here
const tax = money(subtotal * taxRate);
const total = money(subtotal + tax);

const issueDate = new Date().toISOString().slice(0, 10);
const paymentTermsDays = 14;
const dueDateObj = new Date();
dueDateObj.setDate(dueDateObj.getDate() + paymentTermsDays);
const dueDate = dueDateObj.toISOString().slice(0, 10);

// Idempotency: same customer + same period + same total => same invoice
const key = `${customerId}:${periodStart}:${periodEnd}:${total}`;

return [{
  customerId,
  periodStart,
  periodEnd,
  issueDate,
  dueDate,
  lineItems: normalizedItems,
  subtotal,
  tax,
  total,
  key
}];

3.5 Check for Existing Invoice (DB Lookup)#

Before generating anything, check your Invoices table by key (or by customerId + period).

  • If found: stop the branch (NoOp) and log “already exists”.
  • If not found: continue.

This is the difference between a safe system and a system that spams duplicates after a retry.

3.6 Generate an Invoice Number (Safely)#

If you use a DB sequence, generate it in a single atomic statement.

Example (Postgres):

SQL
SELECT nextval('invoice_number_seq') AS seq;

Then format it in n8n:

  • invoiceNumber = 2026- + leftPad(seq, 4)

If you don’t have a DB, use a “settings” row with optimistic locking—but a DB sequence is cleaner.

3.7 Render HTML and Convert to PDF#

Option A: PDF service (fastest)

Use HTTP Request to a PDF API with:

  • HTML content
  • Header/footer (optional)
  • Return URL or binary PDF

Example payload shape (varies by provider):

JSON
{
  "document": {
    "type": "html",
    "content": "<html>...</html>"
  },
  "output": {
    "format": "pdf"
  }
}

Option B: Your own HTML→PDF endpoint (more control)

Host a small Next.js/Node service using Puppeteer. n8n calls it via HTTP and receives a PDF.

This is ideal when you want full control over fonts, layout, or offline generation.

3.8 Persist Invoice + PDF Reference#

Write a record to your invoice storage:

  • status = sent (or draft if you want a manual review step)
  • pdfUrl or pdfFileId (if you store in S3/Drive)
  • sentAt after successful email send

Important: persist before sending, but only mark sentAt after sending.

3.9 Send the Invoice Email#

Use Email node (SMTP or provider integration). Include:

  • Subject: Invoice {invoiceNumber} — Due {dueDate}
  • Body: concise summary + payment method + link
  • Attachment: PDF invoice

Example email body snippet:

  • Total and due date
  • Payment link (Stripe Payment Link or bank instructions)
  • Contact for questions
  • Optional: add “reply-to” to your finance inbox

3.10 Log Events#

Insert an “invoice_sent” event with:

  • timestamp
  • message ID from provider
  • recipient email

This log is what you’ll rely on when a client says “we didn’t receive it”.

# Step 4: Workflow B in n8n — Payment Reminders + Reconciliation#

This workflow runs daily and handles two jobs:

  1. 1
    detect payments (reconciliation)
  2. 2
    send reminders only when still unpaid

4.1 Trigger (Daily Cron)#

Run once daily (e.g., 07:30). If your payment provider posts webhooks, you can also reconcile on webhook events for near real-time updates.

4.2 Fetch Open Invoices#

Query invoices where:

  • status IN ('sent', 'overdue')
  • paidAt IS NULL

If you have many invoices, filter by “due date within last 60 days” to avoid scanning years of history.

4.3 Fetch Payments#

Pick your source:

  • Stripe: list successful charges/payment intents since yesterday
  • PayPal: list transactions
  • Bank CSV: if you can’t access an API yet, upload a CSV to Drive and let n8n parse it daily

For Stripe (conceptually):

  • Fetch payment intents where status = succeeded
  • Include metadata if you can add invoice number to metadata

Best practice: When sending the invoice, include invoice number in the payment reference (or Stripe metadata). Reconciliation becomes deterministic instead of fuzzy.

4.4 Match Payments to Invoices (Function)#

Match in this priority order:

  1. 1
    Exact match by invoiceNumber in payment metadata/description
  2. 2
    Exact match by total + customer
  3. 3
    Fallback match by total + date window (least reliable)

Example matching logic (simplified):

JavaScript
const invoices = $items("Open Invoices").map(i => i.json);
const payments = $items("Payments").map(p => p.json);

const byInvoiceNumber = new Map(invoices.map(inv => [inv.invoiceNumber, inv]));

const reconciled = [];
const unmatchedPayments = [];

for (const pay of payments) {
  const ref = pay.invoiceNumber || pay.reference || "";
  const inv = byInvoiceNumber.get(ref);

  if (inv && Number(pay.amount) === Number(inv.total)) {
    reconciled.push({ invoiceId: inv.invoiceId, paymentId: pay.id, paidAt: pay.paidAt });
  } else {
    unmatchedPayments.push(pay);
  }
}

return reconciled.map(r => ({ ...r }));

Then update invoices:

  • set status = 'paid'
  • set paidAt
  • set externalPaymentRef
  • log event paid_detected

ℹ️ Note: Reconciliation matching is one of the trickiest parts of the pipeline. The priority hierarchy matters: exact invoice number match, then exact amount + customer match, then amount + time window (worst case). Include the invoice number in payment metadata when possible (add it to Stripe metadata, bank transfer reference, or PayPal custom field). This makes matching deterministic instead of probabilistic. Test your matching logic thoroughly with edge cases: multiple invoices with the same amount, partial payments, overpayments, and delayed transactions.

4.5 Reminder Logic (When to Send)#

Define stages that are firm but reasonable:

StageWhenSubject suggestionNotes
Reminder 13 days before due“Upcoming due date: Invoice …”Friendly, proactive
Reminder 23 days after due“Overdue: Invoice …”Direct, include payment link
Reminder 310 days after due“Second notice: Invoice …”Escalate tone slightly

Implementation steps:

  • For each open invoice, calculate daysToDue = dueDate - today.
  • Check if you already sent that reminder stage in Invoice Events.
  • If not, send and log event.

4.6 Send Reminders (Email Node)#

Include:

  • invoice number
  • total and due date
  • payment instructions/link
  • PDF attachment (reuse stored PDF URL/binary)

Stop condition: If reconciliation marks invoice paid, reminder branch must skip it immediately.

# Step 5: Reconciliation Output (Accounting-Ready)#

Reconciliation should produce an exportable view:

  • Paid invoices (invoice number, customer, total, paid date, payment reference)
  • Unpaid invoices (days overdue, last reminder sent)

If you need to push to accounting software, add a final step:

  • HTTP Request to Xero/QuickBooks
  • Create invoice record + payment allocation
  • Store the external accounting ID back on your invoice record

# Step 6: Make It Production-Safe (Idempotency, Errors, Alerts)#

Idempotency Checklist#

  • Generate key and check it before invoice creation.
  • Store sentAt only after successful send.
  • Use unique constraint on key (DB-level) to prevent duplicates.

Error Handling#

Add these n8n nodes/patterns:

  • Error Trigger workflow: capture errors and notify Slack/email.
  • Retries: for transient HTTP errors (PDF API, email provider).
  • Dead-letter queue: store failed invoice IDs for manual retry.

Alerts That Actually Matter#

Notify only when:

  • Invoice generation fails for a customer
  • Email sending fails (bounced/rejected)
  • Reconciliation detects ambiguous matches (same total multiple invoices)
  • Overdue > X days without response

# Common Pitfalls (and How to Avoid Them)#

  1. 1

    Duplicate invoices due to reruns
    Fix: idempotency key + DB unique constraint + “exists?” check.

  2. 2

    PDF generation inconsistencies
    Fix: pin font, avoid remote assets, test in the same renderer as production.

  3. 3

    Reminders sent after payment
    Fix: reconcile first, then remind; log events per stage.

  4. 4

    Bad matching during reconciliation
    Fix: add invoice number to payment reference/metadata; avoid amount-only matching.

  5. 5

    Timezone bugs (due dates off by one)
    Fix: store dates as YYYY-MM-DD for invoices; handle timezone explicitly in n8n settings.

# Example: Minimal n8n Node List (So You Can Build Fast)#

Workflow A (Generate + Send)#

  1. 1
    Cron
  2. 2
    Fetch billables (DB/Sheets/HTTP)
  3. 3
    Split In Batches
  4. 4
    Function (totals + key)
  5. 5
    DB Query (check invoice by key)
  6. 6
    IF (exists?)
  7. 7
    DB Query (next invoice seq)
  8. 8
    Function (build invoiceNumber, render HTML)
  9. 9
    HTTP Request (PDF)
  10. 10
    DB Insert/Update (invoice + pdfUrl)
  11. 11
    Email Send (attach PDF)
  12. 12
    DB Insert (event: sent)

Workflow B (Reconcile + Remind)#

  1. 1
    Cron (daily)
  2. 2
    DB Query (open invoices)
  3. 3
    Fetch payments (Stripe/PayPal/bank)
  4. 4
    Function (match)
  5. 5
    DB Update (mark paid)
  6. 6
    DB Query (invoices needing reminders)
  7. 7
    IF (reminder stage already sent?)
  8. 8
    Email Send (reminder)
  9. 9
    DB Insert (event: reminder_x)

# When to Customize vs. Keep It Standard#

Use this rule: standardize the workflow, customize the edges.

ComponentStandardizeCustomize when
Invoice numberingYesLegal/accounting rules differ by country
Reminder scheduleMostlyDifferent customer segments need different cadence
PDF templateBase templateYou need branded layout or multilingual invoices
ReconciliationYesMultiple payment methods and partial payments
Data sourceNoDepends on your CRM/time-tracking/billing stack

# Next Steps#

# Key Takeaways#

  • Idempotency is critical: always generate a unique key per customer+period+amount, check it before creating an invoice, and enforce it at the database level to prevent duplicate invoicing disasters.
  • Separate workflows for safety: keep invoice generation (Workflow A) separate from reminders and reconciliation (Workflow B) so that failures in one don't cascade to the other.
  • Staged reminders with auto-stop: use a 3-stage reminder schedule (-3 days, +3 days, +10 days), but stop sending immediately once reconciliation detects payment. Log each stage to avoid re-sending.
  • Reconciliation closes the loop: match payments by invoice number (metadata), then by amount+customer, with a fallback to amount+time window. Always include the invoice number in the payment reference at the source.
  • Logging is your safety net: every invoice generated, every email sent, every payment matched, and every reminder scheduled should be logged. These logs are what you'll rely on when a customer disputes a charge or you need to audit the system.
  • Start small, then automate: begin with manual invoice approval, add reminders next, then add reconciliation. You'll learn the edge cases and avoid over-engineering.

# Conclusion#

To automate invoicing process reliably, you need more than a scheduled email: you need consistent invoice rules, idempotency to prevent duplicates, a PDF generation path, staged reminders, and reconciliation that stops reminders the moment payment is detected.

n8n is a strong fit because it connects to your existing tools, keeps logic explicit, and can be hardened with logging and database constraints. Build Workflow A (generate + send), then Workflow B (reconcile + remind), and you’ll have an invoicing pipeline that scales without adding admin work. Explore n8n workflow automation to discover advanced patterns and production-ready solutions for your financial operations.

FAQ

Share
A
Adrijan OmičevićSamioda Team
All articles →

Need help with your project?

We build custom solutions using the technologies discussed in this article. Senior team, fixed prices.