DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

PDF generation from HTML without managing servers

PDF generation from HTML without managing servers

Your app generates invoices. Estimates. Contracts. Shipping labels. Every document becomes a PDF.

Currently: You run wkhtmltopdf on your server. It works... until it doesn't.

Wednesday 3 PM: wkhtmltopdf process hangs on a malformed HTML file. All PDF generation freezes. Customers can't download invoices. Support tickets pile up. By 5 PM, you're SSH'ing into production servers, killing processes manually.

There's a better way. A REST API that generates PDFs from HTML. No server to manage. No hanging processes. No complexity.

The self-hosted PDF generation trap

Most developers start with wkhtmltopdf or similar CLI tools:

const { execSync } = require('child_process');
const fs = require('fs');

function generatePDF(html, filename) {
  fs.writeFileSync('/tmp/input.html', html);

  execSync(`wkhtmltopdf /tmp/input.html /tmp/${filename}.pdf`, {
    timeout: 30000
  });

  const pdf = fs.readFileSync(`/tmp/${filename}.pdf`);
  fs.unlinkSync(`/tmp/input.html`);
  fs.unlinkSync(`/tmp/${filename}.pdf`);

  return pdf;
}
Enter fullscreen mode Exit fullscreen mode

Looks simple. In production: nightmare.

Hidden costs

Process management

  • wkhtmltopdf hangs on malformed HTML → timeout → manual restart
  • No error recovery → customers stuck waiting
  • Requires /tmp cleanup (disk space issues)
  • System resource limits (ulimits) need tuning

Reliability

  • Font rendering differs by system setup
  • Timeouts are silent failures
  • Large files OOM the process
  • No graceful degradation

Scaling

  • Each PDF needs a new process spawn (~2 seconds)
  • Can't run 100 PDFs/second
  • Vertical scaling hits ceiling

Real cost

  • Server overhead: $50-100/month
  • DevOps time (process management): 3-5 hours/month
  • Support burden (timeouts, stuck downloads): high
  • Total: $500-1,000/month in effective cost

Solution: Hosted PDF API

Call an endpoint. Get a PDF back. That's it.

curl -X POST https://api.pagebolt.dev/generate_pdf \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<h1>Invoice #12345</h1><p>Total: $99.99</p>",
    "format": "A4",
    "margin": "1in"
  }'

# Response: PDF binary
Enter fullscreen mode Exit fullscreen mode

No infrastructure. No process management. No timeouts.

Before and after

Self-hosted approach

const express = require('express');
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

app.post('/invoice/:invoiceId', (req, res) => {
  try {
    const html = req.body.html;
    const filename = `invoice-${req.params.invoiceId}`;
    const tmpInput = path.join('/tmp', `${filename}.html`);
    const tmpOutput = path.join('/tmp', `${filename}.pdf`);

    fs.writeFileSync(tmpInput, html);

    // This can hang, timeout, or fail silently
    execSync(`wkhtmltopdf --margin-top 10 --margin-bottom 10 ${tmpInput} ${tmpOutput}`, {
      timeout: 30000,
      stdio: 'pipe'
    });

    const pdf = fs.readFileSync(tmpOutput);

    // Cleanup
    fs.unlinkSync(tmpInput);
    fs.unlinkSync(tmpOutput);

    res.set('Content-Type', 'application/pdf');
    res.set('Content-Disposition', `attachment; filename="${filename}.pdf"`);
    res.send(pdf);
  } catch (error) {
    console.error('PDF generation failed:', error);
    res.status(500).send('PDF generation failed');
  }
});
Enter fullscreen mode Exit fullscreen mode

Cost: $50-100/month infrastructure + 3-5 hours DevOps = ~$700/month effective.

Hosted API approach

const axios = require('axios');

app.post('/invoice/:invoiceId', async (req, res) => {
  try {
    const response = await axios.post(
      'https://api.pagebolt.dev/generate_pdf',
      {
        html: req.body.html,
        format: 'A4',
        margin: '1in'
      },
      {
        headers: {
          'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}`,
          'Content-Type': 'application/json'
        },
        responseType: 'arraybuffer'
      }
    );

    res.set('Content-Type', 'application/pdf');
    res.set('Content-Disposition', `attachment; filename="invoice-${req.params.invoiceId}.pdf"`);
    res.send(response.data);
  } catch (error) {
    res.status(500).send('PDF generation failed');
  }
});
Enter fullscreen mode Exit fullscreen mode

Cost: $29/month (Starter, 5,000 PDFs/month), $0 infrastructure, $0 DevOps = $29/month total.

Real example: Invoice generation + email

E-commerce platform: generate invoice PDF, email to customer, archive to S3.

const axios = require('axios');
const nodemailer = require('nodemailer');
const AWS = require('aws-sdk');

async function generateAndEmailInvoice(order) {
  const htmlTemplate = `
    <h1>Invoice</h1>
    <p>Order ID: ${order.id}</p>
    <table>
      ${order.items.map(item => `
        <tr>
          <td>${item.name}</td>
          <td>$${item.price}</td>
        </tr>
      `).join('')}
    </table>
    <p>Total: $${order.total}</p>
  `;

  // Generate PDF
  const pdfResponse = await axios.post(
    'https://api.pagebolt.dev/generate_pdf',
    { html: htmlTemplate, format: 'A4' },
    {
      headers: { 'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}` },
      responseType: 'arraybuffer'
    }
  );

  const pdf = pdfResponse.data;

  // Email to customer
  const mailOptions = {
    from: 'noreply@mystore.com',
    to: order.customer.email,
    subject: `Invoice for Order #${order.id}`,
    text: 'Your invoice is attached.',
    attachments: [{
      filename: `invoice-${order.id}.pdf`,
      content: pdf
    }]
  };

  await nodemailer.createTransport({...}).sendMail(mailOptions);

  // Archive to S3
  const s3 = new AWS.S3();
  await s3.putObject({
    Bucket: 'invoices-archive',
    Key: `${order.id}.pdf`,
    Body: pdf
  }).promise();

  return { success: true, invoiceId: order.id };
}
Enter fullscreen mode Exit fullscreen mode

Simple, reliable, no process management.

Comparison: wkhtmltopdf vs hosted API

Factor wkhtmltopdf Hosted API
Setup 1-2 hours 10 minutes
Infra cost $50-100/month $0
DevOps time 3-5 hours/month 0 hours
Latency 2-5 seconds 1-2 seconds
Reliability 99% (hangs, timeouts) 99.9% SLA
Scaling Capped at server Unlimited
Font support System-dependent Consistent
Custom styling Limited Full CSS3
Per-PDF cost $0.05-0.10 (infra) $0.02-0.05 (API)

When to use hosted PDF API

✅ User-facing invoice/receipt generation
✅ Automated document pipelines
✅ Report generation at scale
✅ Limited DevOps resources
✅ Unpredictable volume

Keep wkhtmltopdf if:

  • You process 10,000+ PDFs/day (break-even at scale)
  • You have dedicated DevOps
  • Documents have extreme customization

For most: hosted wins.

Getting started

  1. Sign up at pagebolt.dev (free: 100 requests/month)
  2. Get API key (1 minute)
  3. Make first PDF (5 minutes)
  4. Evaluate: Does it solve your use case?

No more hanging processes. No more 3 AM PDF generation failures.

Start free — 100 PDFs/month, no credit card.

Top comments (0)