Billing Email

Send a billing receipt with an invoice PDF attached after a successful payment.

Send a billing receipt with an invoice PDF attached after a successful payment.

Prerequisites

  • A published transactional template with slug billing-receipt containing variables customerName, invoiceNumber, amountPaid, billingDate, and planName
  • An API key with transactional send permissions

Send with a URL attachment

If your invoice PDF is hosted at a URL (e.g. from Stripe or your own storage), pass it via the url field:

import { Owlat } from '@owlat/sdk-js'

const owlat = new Owlat('lm_live_...')

await owlat.transactional.send({
  slug: 'billing-receipt',
  email: 'mira@acme.io',
  dataVariables: {
    customerName: 'Mira Chen',
    invoiceNumber: 'INV-2026-0042',
    amountPaid: '$49.00',
    billingDate: 'March 19, 2026',
    planName: 'Pro',
  },
  attachments: [
    {
      filename: 'invoice-INV-2026-0042.pdf',
      url: 'https://files.stripe.com/invoices/INV-2026-0042.pdf',
      contentType: 'application/pdf',
    },
  ],
})
curl -X POST https://your-deployment.convex.site/api/v1/transactional \
  -H "Authorization: Bearer lm_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "billing-receipt",
    "email": "mira@acme.io",
    "dataVariables": {
      "customerName": "Mira Chen",
      "invoiceNumber": "INV-2026-0042",
      "amountPaid": "$49.00",
      "billingDate": "March 19, 2026",
      "planName": "Pro"
    },
    "attachments": [
      {
        "filename": "invoice-INV-2026-0042.pdf",
        "url": "https://files.stripe.com/invoices/INV-2026-0042.pdf",
        "contentType": "application/pdf"
      }
    ]
  }'

Send with a base64 attachment

If you generate the PDF in memory (e.g. with a library like pdfkit or jspdf), encode it as base64 and use the content field:

import { Owlat } from '@owlat/sdk-js'

const owlat = new Owlat('lm_live_...')

// pdfBuffer is a Buffer from your PDF generation library
const base64Pdf = pdfBuffer.toString('base64')

await owlat.transactional.send({
  slug: 'billing-receipt',
  email: 'mira@acme.io',
  dataVariables: {
    customerName: 'Mira Chen',
    invoiceNumber: 'INV-2026-0042',
    amountPaid: '$49.00',
    billingDate: 'March 19, 2026',
    planName: 'Pro',
  },
  attachments: [
    {
      filename: 'invoice-INV-2026-0042.pdf',
      content: base64Pdf,
      contentType: 'application/pdf',
    },
  ],
})
Attachment limits

You can attach up to 10 files per email with a combined maximum of 10 MB. Keep invoice PDFs lean — strip unnecessary fonts and compress images.

Complete Stripe webhook handler

A real-world example that listens for Stripe's invoice.payment_succeeded event and sends the receipt:

import { Hono } from 'hono'
import Stripe from 'stripe'
import { Owlat } from '@owlat/sdk-js'

const app = new Hono()
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
const owlat = new Owlat(process.env.OWLAT_API_KEY!)

app.post('/webhooks/stripe', async (c) => {
  const payload = await c.req.text()
  const signature = c.req.header('stripe-signature')!

  // 1. Verify the Stripe signature
  const event = stripe.webhooks.constructEvent(
    payload,
    signature,
    process.env.STRIPE_WEBHOOK_SECRET!
  )

  if (event.type === 'invoice.payment_succeeded') {
    const invoice = event.data.object as Stripe.Invoice

    // 2. Send the billing receipt
    await owlat.transactional.send({
      slug: 'billing-receipt',
      email: invoice.customer_email!,
      dataVariables: {
        customerName: invoice.customer_name ?? 'Customer',
        invoiceNumber: invoice.number ?? invoice.id,
        amountPaid: `$${(invoice.amount_paid / 100).toFixed(2)}`,
        billingDate: new Date(invoice.created * 1000).toLocaleDateString('en-US', {
          year: 'numeric',
          month: 'long',
          day: 'numeric',
        }),
        planName: invoice.lines.data[0]?.description ?? 'Subscription',
      },
      attachments: invoice.invoice_pdf
        ? [
            {
              filename: `invoice-${invoice.number}.pdf`,
              url: invoice.invoice_pdf,
              contentType: 'application/pdf',
            },
          ]
        : [],
    })
  }

  return c.json({ received: true })
})

Next steps