Compress Large PDFs for Email Attachments with Node.js
You've built a great invoicing system. It generates beautiful PDF invoices with company logos and product images. There's just one problem: the files are 8MB each, and Gmail rejects attachments over 25MB.
Even when emails go through, large attachments frustrate users on slow connections. They also eat up inbox storage and often get blocked by corporate email filters.
The fix is simple: compress PDFs before sending. A good compression can reduce file sizes by 50-90% without visible quality loss, especially for image-heavy documents.
Quick Example
const axios = require('axios');
const API_TOKEN = 'YOUR_API_TOKEN';
const API_URL = 'https://apdf.io/api/pdf/file/compress';
const STATUS_URL = 'https://apdf.io/api/job/status/check';
// Helper: poll until the async job finishes, then return its result.
async function waitForJob(jobId, maxAttempts = 1200) {
for (let i = 0; i < maxAttempts; i++) {
const check = await axios.post(STATUS_URL, { id: jobId }, {
headers: { 'Authorization': `Bearer ${API_TOKEN}` }
});
const body = check.data;
if (body.status === 'successful') return body.result;
if (body.status === 'failed') throw new Error(body.error || 'Job failed');
await new Promise(r => setTimeout(r, 2000));
}
throw new Error('Job did not finish in time');
}
async function compressPDF(pdfUrl) {
const response = await axios.post(API_URL, {
file: pdfUrl
}, {
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
return await waitForJob(response.data.job_id);
}
// Usage
compressPDF('https://example.com/large-invoice.pdf')
.then(result => {
console.log(`Original: ${result.size_original} bytes`);
console.log(`Compressed: ${result.size_compressed} bytes`);
console.log(`Download: ${result.file}`);
});
The API returns the compressed file URL along with the original and new sizes so you can see the savings.
Real-World Scenario: Email Invoice System
You're building an e-commerce backend that sends order confirmations with PDF invoices attached. Before sending, you need to compress the PDF to ensure deliverability.
const axios = require('axios');
const nodemailer = require('nodemailer');
const API_TOKEN = 'YOUR_API_TOKEN';
const COMPRESS_URL = 'https://apdf.io/api/pdf/file/compress';
const STATUS_URL = 'https://apdf.io/api/job/status/check';
const MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024; // 10MB limit
// Helper: poll until the async job finishes, then return its result.
async function waitForJob(jobId, maxAttempts = 1200) {
for (let i = 0; i < maxAttempts; i++) {
const check = await axios.post(STATUS_URL, { id: jobId }, {
headers: { 'Authorization': `Bearer ${API_TOKEN}` }
});
const body = check.data;
if (body.status === 'successful') return body.result;
if (body.status === 'failed') throw new Error(body.error || 'Job failed');
await new Promise(r => setTimeout(r, 2000));
}
throw new Error('Job did not finish in time');
}
async function compressPDF(pdfUrl) {
const response = await axios.post(COMPRESS_URL, {
file: pdfUrl
}, {
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
return await waitForJob(response.data.job_id);
}
async function downloadPDF(url) {
const response = await axios.get(url, { responseType: 'arraybuffer' });
return Buffer.from(response.data);
}
async function sendInvoiceEmail(customerEmail, invoicePdfUrl, orderNumber) {
console.log(`Processing invoice for order ${orderNumber}...`);
// Step 1: Compress the PDF
const compressed = await compressPDF(invoicePdfUrl);
const savings = Math.round((1 - compressed.size_compressed / compressed.size_original) * 100);
console.log(`Compressed: ${savings}% smaller`);
// Step 2: Check if it's small enough for email
if (compressed.size_compressed > MAX_ATTACHMENT_SIZE) {
throw new Error('PDF still too large after compression');
}
// Step 3: Download the compressed PDF
const pdfBuffer = await downloadPDF(compressed.file);
// Step 4: Send email with attachment
const transporter = nodemailer.createTransport({
host: 'smtp.example.com',
port: 587,
auth: {
user: 'invoices@example.com',
pass: 'your-password'
}
});
await transporter.sendMail({
from: 'invoices@example.com',
to: customerEmail,
subject: `Your Invoice for Order #${orderNumber}`,
text: `Thank you for your order! Please find your invoice attached.`,
attachments: [{
filename: `invoice-${orderNumber}.pdf`,
content: pdfBuffer
}]
});
console.log(`Invoice sent to ${customerEmail}`);
}
// Example usage
sendInvoiceEmail(
'customer@example.com',
'https://your-storage.com/invoices/order-12345.pdf',
'12345'
).catch(console.error);
Batch Compression for Storage Optimization
If you're storing many PDFs, compressing them before upload saves significant storage costs. Here's a script to process multiple files:
const axios = require('axios');
const API_TOKEN = 'YOUR_API_TOKEN';
const API_URL = 'https://apdf.io/api/pdf/file/compress';
const STATUS_URL = 'https://apdf.io/api/job/status/check';
// Helper: poll until the async job finishes, then return its result.
async function waitForJob(jobId, maxAttempts = 1200) {
for (let i = 0; i < maxAttempts; i++) {
const check = await axios.post(STATUS_URL, { id: jobId }, {
headers: { 'Authorization': `Bearer ${API_TOKEN}` }
});
const body = check.data;
if (body.status === 'successful') return body.result;
if (body.status === 'failed') throw new Error(body.error || 'Job failed');
await new Promise(r => setTimeout(r, 2000));
}
throw new Error('Job did not finish in time');
}
async function compressBatch(pdfUrls) {
const results = [];
for (const url of pdfUrls) {
try {
console.log(`Compressing: ${url}`);
const response = await axios.post(API_URL, {
file: url
}, {
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
const data = await waitForJob(response.data.job_id);
const savings = Math.round((1 - data.size_compressed / data.size_original) * 100);
results.push({
original: url,
compressed: data.file,
originalSize: data.size_original,
compressedSize: data.size_compressed,
savings: `${savings}%`
});
console.log(` -> Reduced by ${savings}%`);
// Rate limiting pause
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
console.error(` -> Failed: ${error.message}`);
results.push({ original: url, error: error.message });
}
}
return results;
}
// Process files
const files = [
'https://your-storage.com/reports/q1-2025.pdf',
'https://your-storage.com/reports/q2-2025.pdf',
'https://your-storage.com/reports/q3-2025.pdf'
];
compressBatch(files).then(results => {
console.log('\\nSummary:');
console.table(results);
});
When Compression Works Best
PDF compression is most effective for:
- Image-heavy documents: Invoices with logos, brochures, photo albums (50-90% reduction)
- Scanned documents: Often contain unoptimized images (40-70% reduction)
- Reports with charts: Embedded images can be optimized (30-60% reduction)
Text-only PDFs are usually already small and won't compress much further.
Next Steps
Now that you can compress PDFs, consider these related features:
- Check file info first: Use the Metadata Read endpoint to check file size before deciding whether to compress.
- Merge then compress: If you're combining multiple PDFs with the Merge endpoint, compress the result for optimal file size.
Related tutorials
Compress a Folder of PDFs with Claude Code
Batch compression without the batch script: hand Claude Code a folder of hosted PDFs and the agent loops the compress tool over every file, polls the jobs, and reports honestly — a 74 MB board pack collapses 94%, a lean text file shaves 4%, and an optimized scan keeps its original.
Reduce AWS S3 Costs by Automating PDF Compression
This tutorial shows how to cut AWS S3 storage costs by compressing PDFs before storing them. Using Java and the Apdf Compress API, developers can achieve up to 90% file size reduction on image-heavy PDFs, directly translating into lower storage bills.
Extract Text from Scanned Invoices for Automated Data Entry
Automate data entry from scanned invoices and receipts using Node.js and the Apdf OCR Read API. Extract vendor names, invoice numbers, and totals from image-based PDFs and feed them directly into your accounting system.
Your code made the PDF.
Then it went dark.
Opened, read, re-read, dropped on page 4 — you never see any of it. Share the PDFs you generate through Apdf recipient links, and every signal becomes something you can act on: ping Slack, update the CRM, let an agent follow up. Same account, same API token, one more call.
API · Webhook · MCP