Automating Invoice Generation with Node.js — TongoRender Blog
Back to Blog
tutorialsinvoicenodejsautomation

Automating Invoice Generation with Node.js

Learn how to build an automated invoice generation pipeline with Node.js. Covers Stripe integration, batch processing, email delivery, and PDF creation via API.

TongoRender TeamFebruary 20, 202611 min

Manual invoice creation is tedious and error-prone. Whether you bill clients monthly, process e-commerce orders, or manage subscription payments, automating your invoice pipeline saves time, reduces mistakes, and ensures consistent branding. In this tutorial, we will build a complete Node.js invoice automation system that integrates with Stripe, generates professional PDF invoices via TongoRender, and delivers them by email.

System Architecture

Our automated pipeline has four components:

  1. Data source — Stripe API for payment data (adaptable to any billing system)
  2. Template engine — HTML/CSS invoice template with dynamic data injection
  3. PDF renderer — TongoRender API to convert HTML to polished PDF
  4. Delivery — Nodemailer for email delivery with PDF attachment

Step 1: Fetch Payment Data from Stripe

const Stripe = require('stripe');
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

async function getMonthlyInvoices(year, month) {
  const startTimestamp = Math.floor(new Date(year, month - 1, 1).getTime() / 1000);
  const endTimestamp = Math.floor(new Date(year, month, 0, 23, 59, 59).getTime() / 1000);

  const charges = await stripe.charges.list({
    created: { gte: startTimestamp, lte: endTimestamp },
    limit: 100,
    expand: ['data.customer'],
  });

  return charges.data
    .filter(charge => charge.status === 'succeeded')
    .map(charge => ({
      id: charge.id,
      amount: charge.amount / 100,
      currency: charge.currency.toUpperCase(),
      customerName: charge.customer?.name || 'Unknown',
      customerEmail: charge.customer?.email,
      description: charge.description || 'Service charge',
      date: new Date(charge.created * 1000).toISOString().split('T')[0],
    }));
}

Step 2: Design the Invoice Template

Create a professional invoice template with proper print styling:

function invoiceHTML(invoice, companyInfo) {
  return `
  <!DOCTYPE html>
  <html>
  <head>
    <style>
      body { font-family: 'Segoe UI', sans-serif; color: #333; margin: 0; padding: 40px; }
      .header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; }
      .company-name { font-size: 24px; font-weight: 700; color: #1a1a2e; }
      .invoice-title { font-size: 32px; color: #e63946; text-align: right; }
      .invoice-meta { text-align: right; color: #666; margin-top: 10px; }
      .billing-section { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin: 30px 0; }
      .label { font-size: 12px; text-transform: uppercase; color: #999; letter-spacing: 1px; }
      table { width: 100%; border-collapse: collapse; margin: 30px 0; }
      th { background: #1a1a2e; color: white; padding: 12px 15px; text-align: left; font-size: 13px; }
      td { padding: 12px 15px; border-bottom: 1px solid #eee; }
      .total-row td { font-weight: 700; font-size: 16px; border-top: 2px solid #1a1a2e; }
      .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; color: #999; font-size: 12px; }
    </style>
  </head>
  <body>
    <div class="header">
      <div>
        <div class="company-name">${companyInfo.name}</div>
        <div>${companyInfo.address}</div>
        <div>${companyInfo.taxId}</div>
      </div>
      <div>
        <div class="invoice-title">INVOICE</div>
        <div class="invoice-meta">
          <div>Invoice #: ${invoice.number}</div>
          <div>Date: ${invoice.date}</div>
          <div>Due: ${invoice.dueDate}</div>
        </div>
      </div>
    </div>

    <div class="billing-section">
      <div>
        <div class="label">Bill To</div>
        <div style="margin-top:8px">
          <strong>${invoice.customerName}</strong><br>
          ${invoice.customerAddress || ''}
        </div>
      </div>
    </div>

    <table>
      <thead>
        <tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Amount</th></tr>
      </thead>
      <tbody>
        ${invoice.items.map(item => \`
          <tr>
            <td>${item.description}</td>
            <td>${item.quantity}</td>
            <td>${invoice.currency} ${item.unitPrice.toFixed(2)}</td>
            <td>${invoice.currency} ${(item.quantity * item.unitPrice).toFixed(2)}</td>
          </tr>
        \`).join('')}
        <tr class="total-row">
          <td colspan="3" style="text-align:right">Total</td>
          <td>${invoice.currency} ${invoice.total.toFixed(2)}</td>
        </tr>
      </tbody>
    </table>

    <div class="footer">
      <p>Payment terms: Net 30 days. Please include invoice number in payment reference.</p>
      <p>${companyInfo.bankDetails}</p>
    </div>
  </body>
  </html>`;
}

Step 3: Generate PDF via TongoRender

async function generateInvoicePDF(invoice, companyInfo) {
  const html = invoiceHTML(invoice, companyInfo);

  const response = await fetch('https://api.tongorender.io/v1/pdf', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.TONGORENDER_API_KEY,
    },
    body: JSON.stringify({
      html,
      format: 'A4',
      margin: { top: '10mm', bottom: '10mm', left: '10mm', right: '10mm' },
      printBackground: true,
    }),
  });

  if (!response.ok) throw new Error(`PDF error: ${response.statusText}`);
  return Buffer.from(await response.arrayBuffer());
}

Step 4: Email Delivery

const nodemailer = require('nodemailer');

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: 587,
  auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
});

async function sendInvoiceEmail(to, invoice, pdfBuffer) {
  await transporter.sendMail({
    from: '"Billing" <billing@company.com>',
    to,
    subject: `Invoice #${invoice.number} from Company Name`,
    text: `Please find attached invoice #${invoice.number} for ${invoice.currency} ${invoice.total.toFixed(2)}.`,
    attachments: [{
      filename: `invoice-${invoice.number}.pdf`,
      content: pdfBuffer,
      contentType: 'application/pdf',
    }],
  });
}

Step 5: Putting It All Together

async function processMonthlyBilling() {
  const now = new Date();
  const year = now.getFullYear();
  const month = now.getMonth(); // Previous month

  const payments = await getMonthlyInvoices(year, month);
  const companyInfo = {
    name: 'Acme Corp',
    address: '123 Business St, San Francisco, CA 94102',
    taxId: 'EIN: 12-3456789',
    bankDetails: 'Bank: First National | Account: 1234567890 | Routing: 021000021',
  };

  let invoiceNumber = 1001;
  for (const payment of payments) {
    const invoice = {
      number: `INV-${year}${String(month).padStart(2, '0')}-${invoiceNumber++}`,
      ...payment,
      dueDate: new Date(year, month, 30).toISOString().split('T')[0],
      items: [{ description: payment.description, quantity: 1, unitPrice: payment.amount }],
      total: payment.amount,
    };

    const pdf = await generateInvoicePDF(invoice, companyInfo);
    fs.writeFileSync(`invoices/${invoice.number}.pdf`, pdf);

    if (payment.customerEmail) {
      await sendInvoiceEmail(payment.customerEmail, invoice, pdf);
    }

    console.log(`Processed ${invoice.number} for ${payment.customerName}`);
  }
}

processMonthlyBilling().catch(console.error);

Best Practices

  • Idempotency — Track which invoices have been generated to avoid sending duplicates on retry.
  • Error handling — Wrap each invoice in a try-catch. One failure should not stop the entire batch.
  • Rate limiting — Space out API calls with a small delay to stay within rate limits during large batches.
  • Storage — Always save a copy of the PDF before emailing it, in case email delivery fails.
  • Tax compliance — Include all legally required fields: tax ID, sequential invoice number, itemized amounts, and applicable taxes.

TongoRender's reliable PDF rendering ensures your invoices look professional every time. No more broken layouts or missing fonts.

Automate your invoices with TongoRender — 100 free renders per month, no credit card required.

Share this articleShare on Twitter