✅ PDF Attachments - CORRECT WORKFLOW
You're absolutely right! It doesn't make sense to attach a PDF to an email AFTER it's been sent!
Correct approach: Generate PDF first, then include it in the send request.
🎯 THE RIGHT WAY TO DO IT
Two-Step Workflow:
1. Generate PDF → Get base64 content
2. Send email → Include PDF in attachments
📡 METHOD 1: GENERATE THEN SEND ⭐ RECOMMENDED
Step 1: Generate PDF
curl -X POST 'http://localhost:4000/communications/generate-pdf' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_TOKEN' \
--data '{
"templateType": "invoice",
"templateData": {
"invoiceNumber": "INV-12350",
"invoiceDate": "2025-11-01",
"companyName": "RPA Solution",
"companyAddress": "Your Address",
"companyEmail": "info@rpasolution.com",
"customerName": "John Doe",
"customerEmail": "luisrangelc@gmail.com",
"totalAmount": "$550.00",
"items": [
{
"name": "Product A",
"quantity": 2,
"unitPrice": "$100.00",
"amount": "$200.00"
},
{
"name": "Product B",
"quantity": 3,
"unitPrice": "$116.67",
"amount": "$350.00"
}
],
"subtotal": "$550.00",
"tax": "$0.00",
"paymentTerms": "Net 30",
"notes": "Thank you for your business!"
}
}'
Response:
{
"filename": "invoice-2025-11-01-temp1730123456.pdf",
"content": "JVBERi0xLjQK...BASE64_PDF_CONTENT...",
"mimeType": "application/pdf",
"sizeBytes": 45678
}
Save the content value! You'll use it in step 2.
Step 2: Send Email with PDF
curl -X POST 'http://localhost:4000/communications/send' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_TOKEN' \
--data-raw '{
"businessId": "33b6db4b-51c5-45ee-8d04-c01c2d157f66",
"createdBy": "6c0c4f32-d74a-4a84-892f-3bead447d765",
"channel": "email",
"type": "invoice",
"recipientType": "customer",
"recipientId": "824e2bec-8402-4846-b974-1fd543fa2cb1",
"recipientContact": "luisrangelc@gmail.com",
"subject": "Invoice #12350 - WITH PDF!",
"content": "<h2>Your Invoice is Ready!</h2><p>Dear Customer,</p><p>Please find your invoice <strong>attached as PDF</strong>.</p><p>Thank you for your business!</p>",
"attachments": [
{
"filename": "invoice-12350.pdf",
"content": "PASTE_THE_BASE64_CONTENT_FROM_STEP_1_HERE",
"mimeType": "application/pdf"
}
]
}'
Result: Email sent with PDF attachment! 📎✅
💻 FROM BACKEND CODE
In Your NestJS Service:
import { Injectable } from '@nestjs/common';
import { CommunicationsService } from '@/communications/application/communications.service';
import { GenerateCommunicationPdfUseCase } from '@/communications/application/use-cases/generate-communication-pdf.use-case';
@Injectable()
export class InvoiceService {
constructor(
private readonly communicationsService: CommunicationsService,
private readonly generatePdfUseCase: GenerateCommunicationPdfUseCase,
) {}
async sendInvoiceEmail(invoice: Invoice) {
// Step 1: Generate PDF
const pdf = await this.generatePdfUseCase.execute({
templateType: 'invoice',
templateData: {
invoiceNumber: invoice.number,
invoiceDate: this.formatDate(invoice.date),
dueDate: this.formatDate(invoice.dueDate),
companyName: invoice.business.name,
companyAddress: invoice.business.address,
companyEmail: invoice.business.email,
customerName: invoice.customer.name,
customerEmail: invoice.customer.email,
items: invoice.items.map(item => ({
name: item.product.name,
quantity: item.quantity,
unitPrice: `$${item.unitPrice.toFixed(2)}`,
amount: `$${item.total.toFixed(2)}`
})),
subtotal: `$${invoice.subtotal.toFixed(2)}`,
tax: `$${invoice.tax.toFixed(2)}`,
totalAmount: `$${invoice.total.toFixed(2)}`,
paymentTerms: 'Net 30'
}
});
// Step 2: Send email with PDF
return await this.communicationsService.send({
businessId: invoice.businessId,
createdBy: invoice.createdBy,
recipientType: 'customer',
recipientId: invoice.customerId,
recipientContact: invoice.customer.email,
channel: 'email',
type: 'invoice',
subject: `Invoice ${invoice.number} from ${invoice.business.name}`,
content: `
<h2>Invoice ${invoice.number}</h2>
<p>Dear ${invoice.customer.name},</p>
<p>Please find your invoice attached as PDF.</p>
<p><strong>Amount Due: $${invoice.total.toFixed(2)}</strong></p>
<p>Due Date: ${this.formatDate(invoice.dueDate)}</p>
<p>Thank you for your business!</p>
`,
attachments: [
{
filename: pdf.filename,
content: pdf.content, // Base64 from step 1
mimeType: pdf.mimeType
}
]
});
}
}
🎨 FROM FRONTEND (React/TypeScript)
In Your Frontend Service:
// Step 1: Add PDF generation function
export const generatePdf = async (
templateType: 'invoice' | 'payment' | 'order' | 'general',
templateData: Record<string, unknown>
) => {
const response = await api.post('/communications/generate-pdf', {
templateType,
templateData
});
return response.data;
};
// Step 2: Use in your component
const handleSendInvoiceWithPdf = async (invoice: Invoice) => {
try {
// Generate PDF
const pdf = await generatePdf('invoice', {
invoiceNumber: invoice.number,
invoiceDate: formatDate(invoice.date),
companyName: 'RPA Solution',
customerName: invoice.customerName,
totalAmount: formatCurrency(invoice.total),
items: invoice.items.map(item => ({
name: item.name,
quantity: item.quantity,
unitPrice: formatCurrency(item.price),
amount: formatCurrency(item.total)
}))
});
// Send email with PDF
await sendCommunication({
recipientContact: invoice.customerEmail,
channel: 'email',
type: 'invoice',
subject: `Invoice ${invoice.number}`,
content: '<p>Your invoice is attached.</p>',
attachments: [
{
filename: pdf.filename,
content: pdf.content,
mimeType: pdf.mimeType
}
]
});
toast.success('Invoice sent with PDF!');
} catch (error) {
toast.error('Failed to send invoice');
}
};
🔄 COMPLETE FLOW DIAGRAM
Frontend/API Call
↓
1. POST /communications/generate-pdf
{
templateType: "invoice",
templateData: { ... }
}
↓
Returns: { filename, content (base64), mimeType }
↓
2. POST /communications/send
{
subject: "Invoice #123",
content: "Invoice attached",
attachments: [
{
filename: "invoice.pdf",
content: "BASE64_FROM_STEP_1", ← Include PDF here!
mimeType: "application/pdf"
}
]
}
↓
Email Adapter (SendGrid)
↓
Email Sent WITH PDF Attachment! ✅
↓
Customer Receives Email with PDF! 📧📎
🎯 NEW ENDPOINT EXPLAINED
POST /communications/generate-pdf ✨ NEW!
Purpose: Generate a PDF that you can then include in send request
Request:
{
"templateType": "invoice",
"templateData": { ... }
}
Response:
{
"filename": "invoice-2025-11-01-temp123.pdf",
"content": "JVBERi0xLjQK...base64...",
"mimeType": "application/pdf",
"sizeBytes": 45678
}
Then use the content in:
{
"attachments": [
{
"filename": "invoice.pdf",
"content": "PASTE_CONTENT_HERE", ← From generate-pdf response
"mimeType": "application/pdf"
}
]
}
🧪 COMPLETE TEST EXAMPLE
Test the Correct Workflow:
# Terminal 1: Generate PDF
PDF_RESPONSE=$(curl -X POST 'http://localhost:4000/communications/generate-pdf' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"templateType": "invoice",
"templateData": {
"invoiceNumber": "INV-12350",
"invoiceDate": "2025-11-01",
"companyName": "RPA Solution",
"customerName": "Test Customer",
"totalAmount": "$100.00"
}
}')
# Extract base64 content
PDF_CONTENT=$(echo $PDF_RESPONSE | jq -r '.content')
# Terminal 2: Send with PDF
curl -X POST 'http://localhost:4000/communications/send' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d "{
\"businessId\": \"33b6db4b-51c5-45ee-8d04-c01c2d157f66\",
\"createdBy\": \"6c0c4f32-d74a-4a84-892f-3bead447d765\",
\"channel\": \"email\",
\"type\": \"invoice\",
\"recipientType\": \"customer\",
\"recipientContact\": \"luisrangelc@gmail.com\",
\"subject\": \"Invoice #12350 WITH PDF!\",
\"content\": \"<p>Invoice attached as PDF</p>\",
\"attachments\": [
{
\"filename\": \"invoice-12350.pdf\",
\"content\": \"$PDF_CONTENT\",
\"mimeType\": \"application/pdf\"
}
]
}"
📊 WHAT ABOUT /attach-pdf?
When to Use /attach-pdf:
❌ DON'T USE for normal workflow - Email already sent
✅ DO USE for: Saving PDF records to database for existing communications
✅ DO USE for: Tracking what PDFs were generated
✅ DO USE for: Historical record-keeping
It's more of an archive/tracking feature, not for sending!
🎊 UPDATED API DESIGN
New Endpoints:
POST /communications/generate-pdf ← Generate PDF, get base64
POST /communications/send ← Send with attachments
POST /communications/:id/attach-pdf ← Save PDF record (archive)
GET /communications/:id/attachments ← View PDF records
GET /communications/pdf-templates ← List templates
🚀 TRY THIS NOW
Simple Test with Minimal Data:
# Step 1: Generate PDF
curl -X POST 'http://localhost:4000/communications/generate-pdf' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"templateType": "invoice",
"templateData": {
"invoiceNumber": "INV-TEST",
"invoiceDate": "2025-11-01",
"companyName": "RPA Solution",
"customerName": "Test Customer",
"totalAmount": "$99.99"
}
}' > pdf-response.json
# Check the response
cat pdf-response.json | jq '.'
# You'll see:
# {
# "filename": "invoice-2025-11-01-temp...pdf",
# "content": "JVBERi0xLjQK...", ← Copy this!
# "mimeType": "application/pdf",
# "sizeBytes": 12345
# }
# Step 2: Copy the "content" value and use it in send
# (Or use jq to extract it automatically as shown in complete example above)
💡 EVEN SIMPLER: DIRECT ATTACHMENT
If you already have a PDF file, just send it directly:
# If you have a PDF file locally:
BASE64_PDF=$(base64 -i your-invoice.pdf)
# Send directly
curl -X POST 'http://localhost:4000/communications/send' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d "{
\"businessId\": \"33b6db4b-51c5-45ee-8d04-c01c2d157f66\",
\"createdBy\": \"6c0c4f32-d74a-4a84-892f-3bead447d765\",
\"channel\": \"email\",
\"type\": \"invoice\",
\"recipientContact\": \"luisrangelc@gmail.com\",
\"subject\": \"Invoice #12350\",
\"content\": \"Invoice attached\",
\"attachments\": [
{
\"filename\": \"invoice.pdf\",
\"content\": \"$BASE64_PDF\",
\"mimeType\": \"application/pdf\"
}
]
}"
🎯 RECOMMENDED IMPLEMENTATION
Create a Unified Service Method:
@Injectable()
export class InvoiceEmailService {
constructor(
private readonly generatePdfUseCase: GenerateCommunicationPdfUseCase,
private readonly communicationsService: CommunicationsService,
) {}
/**
* Send invoice email with PDF attachment (all in one!)
*/
async sendInvoiceWithPdf(invoice: Invoice): Promise<Communication> {
// 1. Generate PDF
const pdf = await this.generatePdfUseCase.execute({
templateType: 'invoice',
templateData: this.buildInvoiceData(invoice)
});
// 2. Send with PDF attached
return await this.communicationsService.send({
businessId: invoice.businessId,
createdBy: invoice.createdBy,
recipientType: 'customer',
recipientId: invoice.customerId,
recipientContact: invoice.customerEmail,
channel: 'email',
type: 'invoice',
subject: `Invoice ${invoice.number} from ${invoice.business.name}`,
content: this.buildEmailContent(invoice),
attachments: [
{
filename: pdf.filename,
content: pdf.content,
mimeType: pdf.mimeType
}
]
});
}
private buildInvoiceData(invoice: Invoice) {
return {
invoiceNumber: invoice.number,
invoiceDate: formatDate(invoice.date),
dueDate: formatDate(invoice.dueDate),
companyName: invoice.business.name,
companyAddress: invoice.business.address,
customerName: invoice.customer.name,
customerEmail: invoice.customer.email,
items: invoice.items.map(item => ({
name: item.product.name,
quantity: item.quantity,
unitPrice: formatCurrency(item.unitPrice),
amount: formatCurrency(item.total)
})),
subtotal: formatCurrency(invoice.subtotal),
tax: formatCurrency(invoice.tax),
totalAmount: formatCurrency(invoice.total),
paymentTerms: 'Net 30'
};
}
private buildEmailContent(invoice: Invoice): string {
return `
<h2>Invoice ${invoice.number}</h2>
<p>Dear ${invoice.customer.name},</p>
<p>Please find your invoice attached as PDF.</p>
<p><strong>Amount Due: $${invoice.total.toFixed(2)}</strong></p>
<p>Due Date: ${formatDate(invoice.dueDate)}</p>
<p>Thank you for your business!</p>
`;
}
}
// Usage:
await invoiceEmailService.sendInvoiceWithPdf(invoice);
// ✅ Generates PDF and sends email in one call!
📋 API SUMMARY (UPDATED)
| Endpoint | Purpose | When to Use |
|---|---|---|
POST /communications/generate-pdf | Generate PDF, get base64 | Before sending ⭐ |
POST /communications/send | Send with attachments | Send email with PDF ⭐ |
POST /communications/:id/attach-pdf | Save PDF record | Archive/tracking only |
GET /communications/:id/attachments | View PDF records | See what was attached |
✅ CORRECTED WORKFLOW
Before (Wrong):
1. Send email ❌
2. Attach PDF ❌ (email already sent!)
3. ??? (can't modify sent email)
After (Correct):
1. Generate PDF ✅
2. Get base64 content ✅
3. Send email with PDF in attachments ✅
4. Customer receives email with PDF! ✅
🎊 TRY IT NOW!
Restart backend (to get the new endpoint), then:
# 1. Generate PDF
curl -X POST 'http://localhost:4000/communications/generate-pdf' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"templateType": "invoice",
"templateData": {
"invoiceNumber": "INV-NEW",
"companyName": "RPA Solution",
"customerName": "Your Name",
"totalAmount": "$99.99"
}
}'
# Copy the "content" from response
# 2. Send with that PDF
curl -X POST 'http://localhost:4000/communications/send' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
...
"attachments": [{
"filename": "invoice.pdf",
"content": "PASTE_HERE",
"mimeType": "application/pdf"
}]
}'
# 3. Check your Gmail - PDF attached! 📎
🏆 SUMMARY
You were right! The workflow needed to be redesigned.
New design:
- ✅
generate-pdf→ Returns PDF base64 - ✅
send→ Include PDF in attachments - ✅ Makes logical sense!
- ✅ Works like it should!
Old design issues:
- ❌ attach-pdf after send (doesn't work)
- ❌ Can't modify sent emails
- ❌ Confusing workflow
Now it's perfect! 🎉
Restart backend and try the new /generate-pdf endpoint! 🚀