Here's the plot twist: Stripe can generate invoices, but they're generic white-label PDFs with Stripe's branding plastered everywhere. Your customers get charged, but the receipt feels like it came from a utility company, not your premium SaaS. Enter Velocity X: custom PDF invoices and receipts rendered as React components, stamped with your logo, your colour palette from `brand.json`, ABN/GST declared, payment terms baked in, and delivered via Resend before the customer's email confirmation hits their inbox. One line of code. Your brand, your way, ATO-ready.
Why PDF > Emailed HTML for Tax Documents
Email HTML is fragile. A customer's email client re-wraps text, crushes images, or strips CSS. Headers disappear. Totals misalign. Six months later, the ATO asks for proof of sale, and your HTML receipt renders differently than the customer saw. PDFs are immutable. A PDF invoice looks identical whether opened in Gmail, Outlook, Apple Mail, or printed to paper. The ATO treats PDFs as the canonical record — they're auditable, tamper-evident (digitally signed if you want), and legally binding. A PNG screenshot is not. HTML can be doctored. PDFs are official documents. If you're selling software, courses, or consulting to Australian businesses, a PDF invoice is non-negotiable.
HTML email is fine for a shipping notification. For a tax document, it's a liability. Velocity X renders PDFs server-side using Satori (React-to-PDF) so your invoice is pixel-perfect on screen and paper, every time. Same layout, same colours, same ABN box your accountant expects. Zero rendering drift.
Architecture: Satori + React Templates + Resend Delivery
When a Stripe charge succeeds, a webhook fires. Your backend catches it and runs an async function: query the customer record, fetch the product/quote details, and render a React component as a PDF using Satori. Here's the flow:
// Stripe webhook → generate and deliver invoice PDF
{"\n"}import Satori from 'satori';
{"\n"}import ReactPDF from '@react-pdf/renderer';
{"\n"}import { Resend } from 'resend';
{"\n"}
{"\n"}export async function onStripeCharge(event) {"{"}
{"\n"} const charge = event.data.object;
{"\n"} const customer = await db.customers.findOne({"{"}id: charge.customer_id{"}"}); // Your DB
{"\n"} const invoice = await db.invoices.findOne({"{"}charge_id: charge.id{"}"}); // Your DB
{"\n"}
{"\n"} // Render React invoice component to PDF
{"\n"} const pdf = await Satori(
{"\n"} ,
{"\n"} {"{"}width: 210, height: 297{"}"} // A4 mm → px
{"\n"} );
{"\n"}
{"\n"} // Ship it via Resend
{"\n"} const resend = new Resend(process.env.RESEND_API_KEY);
{"\n"} await resend.emails.send({"{"}
{"\n"} from: 'invoices@yoursite.com',
{"\n"} to: customer.email,
{"\n"} subject: `Invoice ${"{"}invoice.number{"}"}`,
{"\n"} attachments: [{"{"}
{"\n"} filename: `invoice-${"{"}invoice.number{"}"}.pdf`,
{"\n"} content: Buffer.from(pdf).toString('base64')
{"\n"} {"}"}],
{"\n"} react:
{"\n"} {"}"}); // Email body + PDF attachment
{"\n"}{"}"}
Satori renders React as SVG, then converts to PDF. You control the layout with Tailwind utilities (or CSS-in-JS). Resend sends it as an attachment. Customer receives your branded invoice, downloadable and printable. Your PDF lands in your Supabase `invoices` table for later audit. Done.
Brand-Driven Invoice Design from brand.json
Your `brand.json` already holds logo paths, colour palette, company name, and ABN. The invoice template pulls from the same source:
{"\n"}const Invoice = ({"{"}customer, items, total, brandJson, abnNumber{"}"}) => ({"\n"} const brand = brandJson; // Loaded from your content folder{"\n"} return ({"\n"} {"\n"} {"\n"}
{"\n"} {"\n"} Invoice
{"\n"} {"{"}new Date().toLocaleDateString('en-AU'){"}"}}
{"\n"} {"\n"} {"\n"} {"\n"} {"\n"} {"{"}brand.companyName{"}"}
{"\n"} ABN {"{"}abnNumber{"}"}
{"\n"} GST-Registered
{"\n"} {"\n"} {"\n"} {"\n"} {/* Items table: description, qty, unit price, line total */}{"\n"} {"{"}items.map(item => ({"\n"} {"\n"} {"{"}item.description{"}"} {"\n"} ${"{"}item.total.toFixed(2){"}"} {"\n"} {"\n"} }))){"\n"}
{"\n"} {"\n"} {"\n"} Total: ${"{"}total.toFixed(2){"}"}
{"\n"} GST Included
{"\n"} {"\n"} {"\n"} );{"\n"}});{"}}"}
Same brand.json that powers your homepage now colours your invoices. Change your palette once; invoices auto-update. Your logo appears top-left. ABN is declared right below your company name. Colours, fonts, spacing — all consistent with your brand identity. That's design scale.
ATO Compliance: ABN, GST, and Due Date Formatting
Australian invoices have mandatory fields the ATO expects: seller's ABN, buyer's name and ABN (if B2B), itemised GST, and due date. Velocity X bakes these into every PDF. The invoice header declares your ABN and GST registration. The line items table separates base amount and GST. The footer states payment terms (e.g., "Due within 14 days" or "Due on receipt"). A Stripe-generated invoice omits half of this; you're left re-issuing corrected PDFs to your accountant. Velocity X generates the final version immediately. No revisions. Audit-ready on day one.
If you're issuing custom quotes (not Stripe charges), the same React template works for quote-to-invoice workflows. Quote issued Friday, customer approves Monday, invoice auto-renders Tuesday — all branded, all tax-compliant, all in the same template.
Payment Terms, Recurring Contracts, and Custom Quotes
Velocity X invoices aren't limited to one-off Stripe charges. Recurring contracts (e.g., "retainer: 3 months, $5k/month") render with payment schedule baked in. A custom quote template includes line-item discounts, payment milestones, and a validity date ("Quote valid until 30 June"). When the customer approves, you convert it to an invoice — same template, updated status, new PDF. The entire audit trail (quote → approval → invoice → payment) lives in Supabase with PDFs attached. Your accountant and customer both have canonical records. No more "which version did we agree to?"
Six FAQs: Invoice Generation in Production
What if Resend fails to deliver the invoice email?
Resend has a 99.9% uptime SLA, but networks fail. Velocity X stores every generated PDF in Supabase Storage (or a CDN bucket) and marks it with a delivery status. If Resend bounces, you get a webhook alert, and the invoice is retryable via a manual dashboard action or an automated 30-minute retry loop. The customer can also download their invoice from their account dashboard (invoice.publicUrl) without waiting for email. Belt and braces.
Can I add custom line-item taxes (e.g., state tax)?
Yes. The React invoice component accepts an `items` array where each item has `baseAmount`, `taxRate`, and `taxAmount` separately. You can render GST on some items and a different rate on others if your business requires it. Satori renders it; the PDF shows each line. Most Australian SaaS use flat 10% GST, but the template doesn't force that — it's flexible.
Do I need to store PDFs forever?
The ATO's compliance window is 5 years (longer for certain industries). Store PDFs for at least 5 years. Velocity X archives invoices in Supabase Storage with a 6-year retention policy by default. You can extend it or use S3 Glacier for cost savings. The PDF generation is idempotent — you can regenerate an invoice from the database record if needed, and it will be identical (assuming the template hasn't changed).
What if I rebrand mid-year?
Invoices issued before the rebrand render with the old brand.json snapshot. Invoices issued after use the new one. The React component is timestamped, so your audit trail shows which brand version was active when. If you need to regenerate old invoices with new branding (risky — don't do this for tax purposes), Velocity X flags it as a reissue and stores both the original and the regenerated PDF. Never delete the original.
Can I preview invoices before they're sent?
Absolutely. In your admin dashboard, trigger a "preview" button that generates the PDF but doesn't send it. You see the rendered invoice in your browser, check for typos, verify the customer name and total, then approve. Only after approval does Resend ship it. This catches mistakes before they hit inboxes.
What happens if the Satori render crashes (e.g., missing font)?
Satori requires font files to be loaded server-side (e.g., Inter from Fontsource). If a font is missing, the render fails and falls back to a system font, which looks wrong. Velocity X pre-loads all fonts at startup and logs failures loudly. If a font is missing, the Stripe webhook logs an error, and you're alerted. The charge succeeds, but the invoice generation is retried until fonts are fixed. Zero silent failures.
The Bottom Line
Stripe's generic invoices work for Stripe's brand. Your customers bought from you, not Stripe. Your invoice should look like yours — branded, tax-compliant, and professional. Velocity X renders custom PDFs from your `brand.json` and ships them via Resend before the customer's confirmation email arrives. Satori ensures pixel-perfect rendering. Your ABN is declared. GST is itemised. Due date is stated. Your accountant gets an ATO-compliant record. You get a scalable audit trail. Stripe Tax handles the calculation; Velocity X handles the presentation. Sign up today and ship branded invoices in your first hour. Build the product; let Velocity handle the paperwork.