Skip to main content

🚨 PDF Attachment - Immediate Fix Needed

Date: November 5, 2025, 1:00 PM
Critical Issue: Attachments are being lost between queue and send
User Feedback: "Queue works immediately"


🎯 THE SITUATION​

User confirmed: Queue processes immediately (not waiting)

This means:

  • βœ… Queue is working
  • βœ… Jobs process instantly
  • ❌ But attachments are still not being sent!

πŸ› THE BUG​

Since queue works immediately, the attachment content is being lost during the queue serialize/deserialize cycle.

What Happens:​

1. Enqueue called with payload containing:
{
attachments: [{
filename: "invoice.pdf",
content: "JVBERi0x..." (65KB base64 string),
mimeType: "application/pdf"
}]
}

2. Queue stores payload in database as JSON/JSONB
β†’ Payload might be too large
β†’ OR attachments are being excluded

3. Queue immediately retrieves payload
β†’ Attachments might be missing
β†’ OR content field is empty/null

4. executeSend() receives payload
β†’ NO attachments or empty content!

5. Email adapter sends without attachments
β†’ Empty attachment in email!

πŸ”§ THE SOLUTION​

We Need To:​

STOP storing attachment content in the queue payload!

Instead:

  1. βœ… Save attachment metadata to communicationAttachment table
  2. βœ… ALSO save the base64 content to a new column
  3. βœ… Enqueue WITHOUT attachments (too large)
  4. βœ… In executeSend(), fetch attachments FROM database
  5. βœ… Send email with retrieved content

🎯 IMPLEMENTATION PLAN​

Step 1: Add Content Column to Database (1 min)​

Migration needed:

ALTER TABLE communication_attachment 
ADD COLUMN content TEXT;

Step 2: Save Content When Saving Attachments (2 min)​

Update AttachmentHandlerService.saveAttachments():

await this.database
.insertInto("communicationAttachment")
.values({
communicationId,
filename: attachment.filename,
filePath: undefined,
fileUrl: attachment.fileUrl,
mimeType: attachment.mimeType,
sizeBytes: attachment.content ? Buffer.from(attachment.content, "base64").length : undefined,
content: attachment.content, // ← ADD THIS!
createdAt: undefined,
})
.execute();

Step 3: Exclude Attachments from Queue Payload (1 min)​

Update CommunicationsService.send():

// Don't include attachments in queue (too large)
const { attachments, ...payloadWithoutAttachments } = data;

await this.queueService.enqueue({
communicationId: communication.id,
payload: {
...payloadWithoutAttachments, // ← No attachments!
content,
subject,
providerTemplateId,
communicationId: communication.id,
},
...
});

Step 4: Retrieve Attachments in executeSend() (3 min)​

Update CommunicationsService.executeSend():

async executeSend(
payload: SendCommunicationDto & { communicationId: string },
): Promise<void> {
try {
// Fetch attachments from database (not in payload)
const dbAttachments = await this.attachmentHandler.getAttachments(
payload.communicationId,
);

if (dbAttachments && dbAttachments.length > 0) {
// Convert DB format to DTO format
payload.attachments = dbAttachments.map(att => ({
filename: att.filename,
content: att.content, // From database!
mimeType: att.mimeType,
}));

this.logger.log(
`Retrieved ${payload.attachments.length} attachment(s) from database`
);
}

// Send via adapter
const adapter = this.channelFactory.getAdapter(payload.channel);
const result = await adapter.send(payload);
...
}
}

⏱️ TIME ESTIMATE​

  • Database migration: 1 minute
  • Code updates: 5 minutes
  • Testing: 2 minutes
  • Total: ~8 minutes βœ…

🎊 EXPECTED RESULT​

After fix:

  1. βœ… Attachments saved to database WITH content
  2. βœ… Queue payload is smaller (no large base64)
  3. βœ… executeSend retrieves attachments from DB
  4. βœ… Email sent with REAL PDF attachment!

πŸ“‹ NEXT STEPS​

  1. Create database migration
  2. Update AttachmentHandlerService
  3. Update CommunicationsService.send()
  4. Update CommunicationsService.executeSend()
  5. Restart backend
  6. Test!

Ready to implement this fix? πŸš€