You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Invoices] Attach invoice PDF automatically when sending an invoice email #526
When the user confirms the send action, the invoice PDF must be generated and attached to the outgoing email automatically — no manual download required.
SMART Plan — Attach invoice PDF automatically when sending an invoice email
Context/Epic: Core — sub-task of #150 (Invoices epic #15)
Specific
When the send is confirmed, the invoice PDF must be generated and attached automatically. This is currently unbuildable in develop: Modules\Core\Services\PdfGenerationService is an empty stub class here — there is no PDF rendering pipeline to call. A complete, tested implementation (PdfGenerationService::renderInvoiceHtml()/PDF byte generation, ReportTemplateStorage, etc.) exists on the unmerged feature/130-report-builder branch (verified while grounding #411 in this same batch — a clean fast-forward from develop's current HEAD, 7 commits). This sub-task is blocked on that branch landing; building a second, parallel PDF pipeline just for this issue would be wasted effort.
Measurable
Confirming the send generates a PDF for the invoice and attaches it to the outgoing mailable.
Edge cases worth covering: PDF generation failing (malformed template, missing data) should surface as a graceful send failure per #531, not a silent email with no attachment.
Once merged, call PdfGenerationService::renderInvoicePdf($invoice) (or equivalent) from the send action's confirmation handler, attaching the resulting bytes via Mail::attachData().
Filename convention: {$invoice->invoice_number}.pdf, falling back to a generic name for invoices without a number yet (drafts).
"No manual download required" is the whole point of this sub-task — without it, sending an invoice email still requires a separate manual PDF download-and-attach step.
Time-bound
Sprint 7 — blocked on #411/#130 (PdfGenerationService) landing; can't start meaningfully before then.
When the user confirms the "Email Invoice" send action, the invoice PDF must be generated and attached to the outgoing email automatically — no manual download required — matching v1 (application/modules/mailer/helpers/phpmailer_helper.php:176-205, application/modules/mailer/controllers/Mailer.php:189-194).
Specific
Modules/Invoices/Mail/InvoiceMailable::attachments() returns the rendered invoice PDF (reuse InvoiceService::renderHtml() + PDFFactory, the same pipeline as generatePdf()), named <invoice_number>.pdf (fallback invoice-draft-<id>.pdf). Attachment is skipped when the company setting email_pdf_attachment is false (toggle already exists at Modules/Core/Filament/Admin/Pages/Settings.php:575 — v1 key email_pdf_attachment, default ON). Document uploads linked to the invoice (v1: Mdl_uploads::get_invoice_uploads) are attached as additional files.
Measurable
Sending with email_pdf_attachment=true produces exactly one PDF attachment with the invoice-number filename.
Sending with email_pdf_attachment=false produces zero PDF attachments.
Invoice uploads are attached with their original client filenames.
Achievable
Render the PDF eagerly in InvoiceService::sendInvoiceEmail() (or lazily via Attachment::fromData) and pass bytes+filename into InvoiceMailable.
Read the setting via the Setting model scoped to $invoice->company_id — the mailable runs on a queue worker with no Filament tenant, so do not rely on session/tenant helpers.
Attach $invoice->attachments/uploads from storage.
Relevant
v1 always shipped the PDF; recipients of v2 emails currently get body text only. Blocks #480 (reminders attach PDF too).
Dependency note: when e-invoicing lands (#487 NLCIUS/UBL, #126 Peppol), the XML must be attached alongside the PDF (v1 CII behavior, phpmailer_helper.php:181-197) — add a checklist item there, not here.
Time-Bound
One sprint, after #524/#522 groundwork (already merged).
Arrange
Mail::fake(); company with email_pdf_attachment=true; invoice INV-100 with customer email and one upload.
Act
Mount email_invoice action on ListInvoices for the record, submit with defaults.
Activity
nielsdrost7 commentedon Jul 16, 2026
SMART Plan — Attach invoice PDF automatically when sending an invoice email
Specific
When the send is confirmed, the invoice PDF must be generated and attached automatically. This is currently unbuildable in
develop:Modules\Core\Services\PdfGenerationServiceis an empty stub class here — there is no PDF rendering pipeline to call. A complete, tested implementation (PdfGenerationService::renderInvoiceHtml()/PDF byte generation,ReportTemplateStorage, etc.) exists on the unmergedfeature/130-report-builderbranch (verified while grounding #411 in this same batch — a clean fast-forward fromdevelop's current HEAD, 7 commits). This sub-task is blocked on that branch landing; building a second, parallel PDF pipeline just for this issue would be wasted effort.Measurable
it_attaches_the_invoice_pdf_to_the_outgoing_email()#[Test] public function it_attaches_the_invoice_pdf_to_the_outgoing_email(): void { /* Arrange */ Mail::fake(); $invoice = Invoice::factory()->for($this->company)->create(); /* Act */ $this->testLivewire(ListInvoices::class) ->callTableAction('send email', $invoice, data: ['to' => 'client@example.com', 'subject' => 'Invoice', 'body' => 'See attached.']); /* Assert */ Mail::assertSent(fn ($mail) => count($mail->attachments) === 1); }it_names_the_attached_pdf_after_the_invoice_number()#[Test] public function it_names_the_attached_pdf_after_the_invoice_number(): void { /* Arrange */ Mail::fake(); $invoice = Invoice::factory()->for($this->company)->create(['invoice_number' => 'INV-2026-042']); /* Act */ $this->testLivewire(ListInvoices::class) ->callTableAction('send email', $invoice, data: ['to' => 'client@example.com', 'subject' => 'Invoice', 'body' => 'See attached.']); /* Assert */ Mail::assertSent(function ($mail) { return collect($mail->attachments)->first()->as === 'INV-2026-042.pdf'; }); }PdfGenerationService::resolveTemplate()), not a hardcoded one.it_renders_the_attachment_using_the_invoices_resolved_pdf_template()#[Test] public function it_renders_the_attachment_using_the_invoices_resolved_pdf_template(): void { /* Arrange */ Mail::fake(); $invoice = Invoice::factory()->for($this->company)->create(['template' => 'system/invoice/default']); /* Act */ $this->testLivewire(ListInvoices::class) ->callTableAction('send email', $invoice, data: ['to' => 'client@example.com', 'subject' => 'Invoice', 'body' => 'See attached.']); /* Assert */ Mail::assertSent(fn ($mail) => count($mail->attachments) === 1); }Edge cases worth covering: PDF generation failing (malformed template, missing data) should surface as a graceful send failure per #531, not a silent email with no attachment.
Achievable
feature/130-report-builder([Invoices]: Ability to select from multiple invoice templates #411/[Reports]: Report Builder — bands + bricks #130) first —PdfGenerationServiceis an empty stub indevelopotherwise.PdfGenerationService::renderInvoicePdf($invoice)(or equivalent) from the send action's confirmation handler, attaching the resulting bytes viaMail::attachData().{$invoice->invoice_number}.pdf, falling back to a generic name for invoices without a number yet (drafts).Relevant
"No manual download required" is the whole point of this sub-task — without it, sending an invoice email still requires a separate manual PDF download-and-attach step.
Time-bound
Sprint 7 — blocked on #411/#130 (PdfGenerationService) landing; can't start meaningfully before then.
nielsdrost7 commentedon Jul 18, 2026
Sub-task extracted from #169 during consolidation into #150.
Parent: #150
When the user confirms the "Email Invoice" send action, the invoice PDF must be generated and attached to the outgoing email automatically — no manual download required — matching v1 (
application/modules/mailer/helpers/phpmailer_helper.php:176-205,application/modules/mailer/controllers/Mailer.php:189-194).Specific
Modules/Invoices/Mail/InvoiceMailable::attachments()returns the rendered invoice PDF (reuseInvoiceService::renderHtml()+PDFFactory, the same pipeline asgeneratePdf()), named<invoice_number>.pdf(fallbackinvoice-draft-<id>.pdf). Attachment is skipped when the company settingemail_pdf_attachmentis false (toggle already exists atModules/Core/Filament/Admin/Pages/Settings.php:575— v1 keyemail_pdf_attachment, default ON). Document uploads linked to the invoice (v1:Mdl_uploads::get_invoice_uploads) are attached as additional files.Measurable
email_pdf_attachment=trueproduces exactly one PDF attachment with the invoice-number filename.email_pdf_attachment=falseproduces zero PDF attachments.Achievable
InvoiceService::sendInvoiceEmail()(or lazily viaAttachment::fromData) and pass bytes+filename intoInvoiceMailable.Settingmodel scoped to$invoice->company_id— the mailable runs on a queue worker with no Filament tenant, so do not rely on session/tenant helpers.$invoice->attachments/uploads from storage.Relevant
v1 always shipped the PDF; recipients of v2 emails currently get body text only. Blocks #480 (reminders attach PDF too).
Dependency note: when e-invoicing lands (#487 NLCIUS/UBL, #126 Peppol), the XML must be attached alongside the PDF (v1 CII behavior,
phpmailer_helper.php:181-197) — add a checklist item there, not here.Time-Bound
One sprint, after #524/#522 groundwork (already merged).
Arrange
Mail::fake(); company withemail_pdf_attachment=true; invoiceINV-100with customer email and one upload.Act
Mount
email_invoiceaction onListInvoicesfor the record, submit with defaults.Assert
Mail::assertQueued(InvoiceMailable::class, fn ($m) => collect($m->attachments())->count() >= 1)INV-100.pdfemail_pdf_attachment=false, queued mailable has no PDF attachment