β 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! π