Any SaaS application is incomplete without a billing and invoicing feature. While Stripe makes billing easy, invoicing with your own branding is still a task. We built an open-source starter kit that can help you spin up your own SaaS in minutes, and it includes an invoicing feature.
SeaNotes is an open-source SaaS starter kit that gives you a production-ready foundation for building real applications quickly. At its core, it is a full-stack notes app, that comes preconfigured with services that most SaaS products need: authentication with NextAuth.js, billing with Stripe, transactional email through Resend, file uploads with DigitalOcean Spaces, a PostgreSQL database on DigitalOcean, AI features powered by DigitalOcean Gradient Platform, and one-click deployment to DigitalOcean App Platform.
Using this starter kit, you can either build on top of it, or build your own applications using parts of it. In this article, you will learn how to build an AI-powered invoicing system tailored to your brand’s needs using DigitalOcean’s Gradient Platform, Resend and Stripe.
After reading this article, you will be able to:
Here’s a demo of the invoicing service:
The invoicing service is built to generate invoices with your own branding. It uses the following four layers in an orchestrated microservices architecture:
When someone clicks the “Email Invoice” button, here’s what happens:
As shown in the diagram above, the invoice generator is built using four major components:
The billing and email delivery layers already exist in the starter kit. The main component for the invoice generator is the AI layer, and in the next step, we will see how to build it.
To build the invoice, we will be using the serverless inference feature of the DigitalOcean Gradient Platform.
By using DigitalOcean’s Serverless Inferencing feature for this demo, we get direct API access to models from OpenAI, Anthropic, and Llama. It also lets us use open-source models without handling infra ourselves, and makes it easy to swap out LLMs through a single API and billing system instead of juggling multiple keys. Since it follows a stateless approach it integrates easily with our application logic, giving us complete control over prompts while removing the overhead of scaling and managing model access across providers. This gives us full control over how we connect the AI models to our application and helps us focus only on the AI application logic and model performance.
Here’s how we provision the endpoint from DigitalOcean’s control panel:
Create
..env
file under DO_INFERENCE_API_KEY=your-do-inference-key
When you click on the Email invoice
button:
The code below is what does the above process:
export class InvoiceService implements ConfigurableService {
async generateInvoice(invoiceData: InvoiceData): Promise<GeneratedInvoice> {
const prompt = this.buildInvoicePrompt(invoiceData);
const response = await this.client.chat.completions.create({
model: 'llama3-8b-instruct',
messages: [
{
role: "system",
content: "You are a professional invoice generator. Create beautiful, professional invoices in HTML format."
},
{
role: "user",
content: prompt
}
],
max_tokens: 2000,
temperature: 0.1
});
return this.parseInvoiceResponse(response);
}
}
Here’s how the invoiceData
gets populated with data from Stripe:
// 1. Fetch plan details from Stripe API
const billingService = await createBillingService();
const plans = await billingService.getProducts(); // Calls Stripe API
// 2. Find the user's specific plan
const selectedPlan = plans.find(plan =>
plan.priceId === process.env.STRIPE_PRO_PRICE_ID
);
// 3. Assemble invoice data from Stripe + database
const invoiceData = prepareInvoiceData(userDetails, selectedPlan, subscription.id);
The getProducts()
method calls Stripe’s API to retrieve:
Then prepareInvoiceData()
combines this Stripe data with user info from the database to create the complete invoiceData object that gets sent to the serverless endpoint.
You can find the entire code in the `invoiceService.ts` file.
What happens next is, the endpoint returns a JSON response with:
Here’s an example of how the JSON response looks like:
{
"html": "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Invoice - INV-20241201-0001</title><style>* { box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; margin: 0; padding: 10px; background-color: #f5f5f5; line-height: 1.6; } .container { max-width: 600px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; } .header { background: #0061EB; color: white; padding: 30px 20px; text-align: center; } .header h1 { margin: 0 0 10px 0; font-size: 28px; font-weight: 600; } .header h2 { margin: 0; font-size: 20px; font-weight: 400; opacity: 0.9; } .content { padding: 30px 20px; } .invoice-details { display: flex; flex-direction: column; gap: 20px; margin-bottom: 30px; } @media (min-width: 600px) { .invoice-details { flex-direction: row; justify-content: space-between; } } .customer-info, .invoice-info { flex: 1; } .invoice-info { text-align: left; } @media (min-width: 600px) { .invoice-info { text-align: right; } } .customer-info h3, .invoice-info h3 { margin: 0 0 10px 0; font-size: 16px; color: #333; } .customer-info p, .invoice-info p { margin: 0; font-size: 14px; color: #666; } .item { border-bottom: 1px solid #eee; padding: 20px 0; } .item h3 { margin: 0 0 10px 0; font-size: 18px; color: #333; } .item p { margin: 0 0 15px 0; color: #666; } .total { font-size: 18px; font-weight: bold; margin-top: 20px; padding-top: 20px; border-top: 2px solid #0061EB; color: #333; } .features { margin-top: 15px; } .features strong { display: block; margin-bottom: 8px; color: #333; } .features li { margin-bottom: 4px; } .support-section { margin-top: 30px; text-align: center; padding: 20px; background-color: #f8f9fa; border-radius: 8px; } .support-section p { margin: 0 0 15px 0; color: #666; font-size: 14px; } .contact-button { display: inline-block; background: #0061EB; color: white !important; text-decoration: none; padding: 12px 24px; border-radius: 6px; font-weight: 500; font-size: 14px; transition: background-color 0.2s; min-width: 140px; text-align: center; border: none; cursor: pointer; } .contact-button:hover { background: #0051c3; } .contact-button:active { background: #004094; } .footer { margin-top: 20px; text-align: center; color: #666; font-size: 12px; } .footer p { margin: 5px 0; } @media (max-width: 480px) { body { padding: 5px; } .header { padding: 20px 15px; } .header h1 { font-size: 24px; } .header h2 { font-size: 18px; } .content { padding: 20px 15px; } .contact-button { display: block !important; width: 100% !important; text-align: center !important; margin-top: 10px !important; box-sizing: border-box !important; } .support-section { padding: 15px !important; } }</style></head><body><div class=\"container\"><div class=\"header\"><h1>SeaNotes</h1><h2>Invoice</h2></div><div class=\"content\"><div class=\"invoice-details\"><div class=\"customer-info\"><h3>Bill To:</h3><p><strong>John Doe</strong><br>john@example.com</p></div><div class=\"invoice-info\"><h3>Invoice Details:</h3><p><strong>Invoice #:</strong> INV-20241201-0001<br><strong>Date:</strong> 8/25/2025<br><strong>Subscription ID:</strong> sub_123</p></div></div><div class=\"item\"><h3>Pro Plan</h3><p>Advanced features for power users</p><div class=\"features\"><strong>Features included:</strong><ul><li>Unlimited notes</li><li>Real-time sync</li><li>Priority support</li></ul></div><div class=\"total\"><strong>Total: $12</strong><br><small>Billed monthly</small></div></div><div class=\"support-section\"><p>Thank you for your subscription!</p><p>If you have any questions about this invoice, please contact our support team.</p><a href=\"mailto:support@seanotes.com\" class=\"contact-button\">Contact Support</a></div><div class=\"footer\"><p>SeaNotes</p><p>This is an automatically generated invoice.</p></div></div></div></body></html>",
"text": "INVOICE - INV-20241201-0001\n\nSeaNotes\nInvoice Date: 8/25/2025\n\nBill To:\nJohn Doe\njohn@example.com\n\nSubscription ID: sub_123\n\nITEM:\nPro Plan\nAdvanced features for power users\n\nFeatures included:\n- Unlimited notes\n- Real-time sync\n- Priority support\n\nTOTAL: $12\nBilled monthly\n\nThank you for your subscription!\nIf you have any questions, please contact our support team at support@seanotes.com",
"subject": "Invoice #INV-20241201-0001 - Pro Plan Subscription"
}
This structured response is then passed to the PDF generation service to create the final invoice document that users can receive via email.
The PDF service converts the AI-generated HTML invoice into a professional PDF document by:
Here’s how the implementation works:
export class PDFService {
async generateInvoicePDF(html: string): Promise<Buffer> {
const browser = await this.getBrowser();
const page = await browser.newPage();
// Inject professional styling and responsive design
const completeHTML = this.injectInvoiceStyles(html);
await page.setContent(completeHTML, {
waitUntil: ['networkidle0', 'domcontentloaded'],
timeout: 30000
});
const pdfBuffer = await page.pdf({
format: 'A4',
margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' },
printBackground: true,
deviceScaleFactor: 2
});
return Buffer.from(pdfBuffer);
}
}
You can find the entire code on GitHub.
This PDF buffer is then passed to Resend email service for delivery to users.
The final part is to make this invoice available via email and for that we will be using the existing Resend integration that the starter kit already has.
The code to email integration with Resend can be found in the `generate-invoice/route.tsx` file. What the service does is:
The code below is responsible for sending the AI generated invoice in a PDF format via email:
// Send invoice via email with PDF attachment
const emailService = await createEmailService();
if (emailService.isEmailEnabled()) {
// Prepare email attachments
const attachments = [];
if (pdfBuffer && pdfFilename) {
attachments.push({
filename: pdfFilename,
content: pdfBuffer,
contentType: 'application/pdf'
});
}
await emailService.sendReactEmail(
userDetails.email,
generatedInvoice.subject,
<InvoiceEmail
invoiceHtml={generatedInvoice.html}
customerName={userDetails.name}
planName={selectedPlan.name}
amount={selectedPlan.amount}
invoiceNumber={invoiceData.invoiceNumber}
fromEmail={serverConfig.Resend.fromEmail || 'support@seanotes.com'}
/>,
attachments
);
}
And that’s how you can simply make your application be AI-enabled.
This article can be considered as:
With the starter kit as your foundation, you can move from simple inference tasks to full AI agents that handle more complex, context-aware workflows. For example:
With the starter kit, you can start small like with invoice generation and grow into any of these use cases as your product matures.
Here are some resources related to the starter kit that can help you learn more:
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
A Developer Advocate by profession. I like to build with Cloud, GenAI and can build beautiful websites using JavaScript.
I help Businesses scale with AI x SEO x (authentic) Content that revives traffic and keeps leads flowing | 3,000,000+ Average monthly readers on Medium | Sr Technical Writer @ DigitalOcean | Ex-Cloud Consultant @ AMEX | Ex-Site Reliability Engineer(DevOps)@Nutanix
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
Sign up and get $200 in credit for your first 60 days with DigitalOcean.*
*This promotional offer applies to new accounts only.