Automating PDF Document Generation in AWS Lambda: A Step-by-Step Guide for Developers
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 DingoConclusion
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.