Back to BlogDevelopment

Automating PDF Document Generation in AWS Lambda: A Step-by-Step Guide for Developers

October 22, 2025
10 min read

Generate invoices, reports, certificates, and contracts at scale using AWS Lambda and Puppeteer. This serverless approach handles thousands of PDFs daily without managing servers, with costs under $0.001 per document.

Why AWS Lambda for PDF Generation?

  • Cost-effective: Pay only when generating PDFs ($0.0000166667 per GB-second)
  • Scalable: Handle 1 or 10,000 PDFs simultaneously
  • No server management: AWS handles infrastructure
  • Fast: Typical generation time: 800ms-2s per document
  • Integrated: Works seamlessly with S3, DynamoDB, API Gateway

Cost Example

Scenario: Generate 10,000 invoices per month

  • Lambda execution: 1.5s avg @ 1024MB = $2.50/month
  • S3 storage (10,000 PDFs @ 200KB avg): $0.23/month
  • S3 requests: $0.05/month
  • Total: $2.78/month ($0.000278 per PDF)

Architecture Overview

PDF Generation Flow: 1. API Gateway receives request 2. Lambda function triggers 3. Fetch data from DynamoDB/RDS 4. Puppeteer renders HTML template 5. Generate PDF in /tmp directory 6. Upload to S3 bucket 7. Return S3 signed URL (valid 1 hour) 8. Optional: Send email via SES with PDF attachment Total time: 1.2s average

Implementation: Invoice Generator

Step 1: Set Up Lambda Layer with Puppeteer

# Create layer directory mkdir -p pdf-layer/nodejs/node_modules cd pdf-layer/nodejs # Install dependencies npm install puppeteer-core @sparticuz/chromium # Create layer zip and upload cd .. zip -r pdf-layer.zip . aws lambda publish-layer-version \ --layer-name puppeteer-chromium \ --zip-file fileb://pdf-layer.zip \ --compatible-runtimes nodejs18.x nodejs20.x

Step 2: Lambda Function Code

const chromium = require('@sparticuz/chromium') const puppeteer = require('puppeteer-core') const AWS = require('aws-sdk') const s3 = new AWS.S3() exports.handler = async (event) => { const { invoiceId, customerId } = JSON.parse(event.body) // Launch headless browser const browser = await puppeteer.launch({ args: chromium.args, defaultViewport: chromium.defaultViewport, executablePath: await chromium.executablePath(), headless: chromium.headless, }) const page = await browser.newPage() const html = generateInvoiceHTML(invoiceId, customerId) await page.setContent(html, { waitUntil: 'networkidle0' }) // Generate PDF const pdf = await page.pdf({ format: 'A4', printBackground: true, margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' } }) await browser.close() // Upload to S3 const key = `invoices/${invoiceId}.pdf` await s3.putObject({ Bucket: process.env.PDF_BUCKET, Key: key, Body: pdf, ContentType: 'application/pdf' }).promise() // Generate signed URL (valid 1 hour) const url = s3.getSignedUrl('getObject', { Bucket: process.env.PDF_BUCKET, Key: key, Expires: 3600 }) return { statusCode: 200, body: JSON.stringify({ pdfUrl: url, key }) } }

Step 3: Configure Lambda Function

# Function configuration Memory: 1024 MB (Puppeteer needs memory for Chrome) Timeout: 30 seconds Environment Variables: - PDF_BUCKET: your-pdf-bucket-name # IAM role policies: s3:PutObject and s3:GetObject on your-pdf-bucket-name/*

Advanced Features

1. Dynamic Data from Database

const invoice = await dynamodb.get({ TableName: 'Invoices', Key: { invoiceId } }).promise() const customer = await dynamodb.get({ TableName: 'Customers', Key: { customerId: invoice.Item.customerId } }).promise() const html = ` <h2>${customer.Item.companyName}</h2> <p>${customer.Item.address}</p> ... `

2. Email PDF Attachments via SES

await ses.sendRawEmail({ RawMessage: { Data: createMimeEmail({ to: customer.email, subject: `Invoice #${invoiceId}`, text: 'Please find your invoice attached.', attachments: [{ filename: `invoice-${invoiceId}.pdf`, content: pdf, contentType: 'application/pdf' }] }) } }).promise()

Performance Optimization

1. Warm Starts with Provisioned Concurrency

aws lambda put-provisioned-concurrency-config \ --function-name pdf-generator \ --provisioned-concurrent-executions 2 # Reduces cold start from 8s to 0s # Cost: $10/month for 2 instances

2. Caching Templates in S3

// Load template once, reuse across invocations let cachedTemplate = null if (!cachedTemplate) { const template = await s3.getObject({ Bucket: 'templates', Key: 'invoice-template.html' }).promise() cachedTemplate = template.Body.toString('utf-8') } const html = cachedTemplate .replace('{{invoiceId}}', invoiceId) .replace('{{customerName}}', customer.name)

Common Pitfalls

  • /tmp storage limits: Max 512MB in /tmp (clean up after generating)
  • Memory allocation: Puppeteer needs 1024MB minimum, 1536MB recommended
  • Timeout configuration: Set to 30s (complex PDFs can take 10-15s)
  • Large PDFs: Files >6MB should stream to S3, not return inline
  • Fonts: Self-host fonts or use web fonts (don't rely on system fonts)

Serverless Document Automation

SnapIT Software offers pre-built serverless document generation templates for invoices, reports, certificates, and more. Deploy in minutes with our AWS CDK infrastructure.

Explore Docs Dingo

Conclusion

AWS Lambda PDF generation is cost-effective, scalable, and surprisingly simple. For $0.0003 per document, you can generate professional invoices, reports, and certificates without managing servers. The serverless model scales automatically from 1 to 10,000 PDFs per minute.

Start with the basic Puppeteer example above, then add database integration, custom branding, and email delivery as needed. Deploy the function, test with a few documents, then scale to production -- AWS handles the rest.