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:
- Data source — Stripe API for payment data (adaptable to any billing system)
- Template engine — HTML/CSS invoice template with dynamic data injection
- PDF renderer — TongoRender API to convert HTML to polished PDF
- 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.