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] Email action opens modal pre-filled with recipient, subject, and body from email template #522
It's in the communicable pivot table, just Like where you put those fields for the CC email addresses.
Unless it gets too complicated, but otherwise you'll have 1 email address field in the Relations table and dozens in the communicable table, but also the email address from Relations can be the same as the ones in communicable.
Hope it makes sense to stuff all those fields in anything related to communications: phone, email, whatsapl into that single table
SMART Plan — Email action opens modal pre-filled with recipient, subject, and body
Context/Epic: Core — sub-task of #150 (Invoices epic #15)
Specific
Give the invoice list's "Send Email" action a pre-filled modal instead of firing immediately. Today, Action::make('send email') in InvoicesTable (Modules/Invoices/Filament/Company/Resources/Invoices/Tables/InvoicesTable.php:117-128) has no form/modal at all — it calls app(InvoiceService::class)->sendInvoiceEmail($record) directly from ->action(), and that method does not exist anywhere in InvoiceService or any other class in the codebase (confirmed via a codebase-wide grep). Clicking "Send Email" today throws a fatal Error: Call to undefined method Modules\Invoices\Services\InvoiceService::sendInvoiceEmail(). Separately: the recipient can't simply be $record->customer->email as the parent issue (#150) and its sibling sub-tasks assume — Relation (the customer model) has no email column; Relation::getCustomerEmailAttribute() reads $this->email, which isn't backed by any column, so it always resolves to null. A client's email actually lives in the polymorphic Communication model (Relation::communications(): MorphMany, communication_type = 'email'). This sub-task's pre-fill logic needs to resolve through that relation, not a direct attribute.
Measurable
The "send email" action gains a form/schema with to, subject, and body fields (currently has none).
Frontend/Livewire: As a company admin, clicking "Send Email" on an invoice should open a modal instead of sending immediately, so I can review before it goes out.
Add a primary_email-style resolver to Relation that reads through communications() (where('communication_type', CommunicationType::EMAIL->value)->where('is_primary', true)), since no existing code path returns a plain email string for a client today.
Replace the current no-form Action::make('send email') with a form-carrying Action (->schema([TextInput::make('to'), TextInput::make('subject'), RichEditor/Textarea::make('body')])) in InvoicesTable.
Pre-fill via ->fillForm(fn (Invoice $record) => [...]), resolving to from the new Relation accessor, subject from a configurable default string, and body from the company's EmailTemplate (matched on title for now, since EmailTemplate has no invoice-specific trigger/type field yet — see [Invoices]: Email Template Tags — add payment method variable #366's grounding on this same gap).
Tests live in Modules/Invoices/Tests/Feature/InvoiceEmailModalTest.php (new file).
Relevant
This is the first slice of #150 (the epic's "implement the send-email stub" sub-issue) — without a modal, the user has no chance to review recipient/subject/body before anything is sent, and today the action doesn't even run without erroring.
Time-bound
Sprint 6 — first of the six #150 sub-tasks in this batch; #524 depends on this modal existing.
Activity
KhawarMehfooz commentedon Jul 16, 2026
@nielsdrost7 while working on this issue I found that there is no email field for customer. Is it implemented?
While creating a relation, there is no field to enter customer email, only customer name is entered.
nielsdrost7 commentedon Jul 16, 2026
It's in the
communicablepivot table, just Like where you put those fields for the CC email addresses.Unless it gets too complicated, but otherwise you'll have 1 email address field in the Relations table and dozens in the communicable table, but also the email address from
Relationscan be the same as the ones incommunicable.Hope it makes sense to stuff all those fields in anything related to communications: phone, email, whatsapl into that single table
nielsdrost7 commentedon Jul 16, 2026
SMART Plan — Email action opens modal pre-filled with recipient, subject, and body
Specific
Give the invoice list's "Send Email" action a pre-filled modal instead of firing immediately. Today,
Action::make('send email')inInvoicesTable(Modules/Invoices/Filament/Company/Resources/Invoices/Tables/InvoicesTable.php:117-128) has no form/modal at all — it callsapp(InvoiceService::class)->sendInvoiceEmail($record)directly from->action(), and that method does not exist anywhere inInvoiceServiceor any other class in the codebase (confirmed via a codebase-wide grep). Clicking "Send Email" today throws a fatalError: Call to undefined method Modules\Invoices\Services\InvoiceService::sendInvoiceEmail(). Separately: the recipient can't simply be$record->customer->emailas the parent issue (#150) and its sibling sub-tasks assume —Relation(thecustomermodel) has noemailcolumn;Relation::getCustomerEmailAttribute()reads$this->email, which isn't backed by any column, so it always resolves tonull. A client's email actually lives in the polymorphicCommunicationmodel (Relation::communications(): MorphMany,communication_type = 'email'). This sub-task's pre-fill logic needs to resolve through that relation, not a direct attribute.Measurable
to,subject, andbodyfields (currently has none).#[Test] public function it_opens_a_modal_with_recipient_subject_and_body_fields_when_sending_an_invoice_email(): void { /* Arrange */ $client = Relation::factory()->for($this->company)->create(); $client->communications()->create([ 'company_id' => $this->company->id, 'communication_type' => CommunicationType::EMAIL->value, 'communication_value' => 'client@example.com', 'is_primary' => true, ]); $invoice = Invoice::factory()->for($this->company)->create(['customer_id' => $client->id]); /* Act */ $component = $this->testLivewire(ListInvoices::class) ->mountTableAction('send email', $invoice); /* Assert */ $component->assertSchemaStateSet(['to' => 'client@example.com']); }Invoice #{number}), not left blank.it_prefills_the_subject_with_the_invoice_number()#[Test] public function it_prefills_the_subject_with_the_invoice_number(): void { /* Arrange */ $invoice = Invoice::factory()->for($this->company)->create(['invoice_number' => 'INV-2026-001']); /* Act */ $component = $this->testLivewire(ListInvoices::class) ->mountTableAction('send email', $invoice); /* Assert */ $component->assertSchemaStateSet(['subject' => 'Invoice #INV-2026-001']); }EmailTemplate, when one exists.it_prefills_the_body_from_the_companys_invoice_email_template()#[Test] public function it_prefills_the_body_from_the_companys_invoice_email_template(): void { /* Arrange */ EmailTemplate::factory()->for($this->company)->create(['title' => 'Invoice Default', 'body' => 'Please find your invoice attached.']); $invoice = Invoice::factory()->for($this->company)->create(); /* Act */ $component = $this->testLivewire(ListInvoices::class) ->mountTableAction('send email', $invoice); /* Assert */ $component->assertSchemaStateSet(['body' => 'Please find your invoice attached.']); }it_leaves_the_recipient_blank_when_the_client_has_no_email_on_file()#[Test] public function it_leaves_the_recipient_blank_when_the_client_has_no_email_on_file(): void { /* Arrange */ $client = Relation::factory()->for($this->company)->create(); $invoice = Invoice::factory()->for($this->company)->create(['customer_id' => $client->id]); /* Act */ $component = $this->testLivewire(ListInvoices::class) ->mountTableAction('send email', $invoice); /* Assert */ $component->assertSchemaStateSet(['to' => '']); }Achievable
primary_email-style resolver toRelationthat reads throughcommunications()(where('communication_type', CommunicationType::EMAIL->value)->where('is_primary', true)), since no existing code path returns a plain email string for a client today.Action::make('send email')with a form-carryingAction(->schema([TextInput::make('to'), TextInput::make('subject'), RichEditor/Textarea::make('body')])) inInvoicesTable.->fillForm(fn (Invoice $record) => [...]), resolvingtofrom the newRelationaccessor,subjectfrom a configurable default string, andbodyfrom the company'sEmailTemplate(matched on title for now, sinceEmailTemplatehas no invoice-specific trigger/type field yet — see [Invoices]: Email Template Tags — add payment method variable #366's grounding on this same gap).Modules/Invoices/Tests/Feature/InvoiceEmailModalTest.php(new file).Relevant
This is the first slice of #150 (the epic's "implement the send-email stub" sub-issue) — without a modal, the user has no chance to review recipient/subject/body before anything is sent, and today the action doesn't even run without erroring.
Time-bound
Sprint 6 — first of the six #150 sub-tasks in this batch; #524 depends on this modal existing.
Email action opens modal pre-filled with recipient, subject, and body…
Email action opens modal pre-filled with recipient, subject, and body…