BuiltEmail workspace

Insurance policy closed

Source: App\Mail\InsurancePolicyClosed, app/Mail/InsurancePolicyClosed.php; resources/views/emails/insurance/policy_closed.blade.php using mail::message. Subject: Your insurance policy has been closed! (unchanged). Suggested preheader: Insurance policy {{policy_id}} has been closed.

TokenExisting sourceHandling
{{first_name}}user.first_name ?? '', user = policy.userOptional escaped greeting; remove the whole marked block if empty/null, without inventing a name.
{{policy_id}}policy.policy_idRequired original display. There is no N/A fallback in this source; preserve supplied empty/0 rather than copying the order-completed fallback.
{{closure_reason}}policy.closure_reasonRequired escaped multiline row, unconditional even if supplied empty/0. Do not replace it with a generic cancellation/expiry reason.
{{app_name}}config('app.name')Original Thanks sign-off.
{{preheader}}Source policy-ID/closure summary aboveEscaped text; no refund, coverage, claim or processing-time promise.

No other optional business sections, workflow URLs, attachments/PDFs or legacy duplicate link fields exist. Default Built logo/optional {{logo_url}} and actual CID/approved HTTPS shared assets follow [shared-footer](../shared-footer.md). Missing mandatory policy/reason/user routing context needs an explicit consuming-project decision, not a guessed identifier or recipient.

Trigger and native behavior

InsurancePolicyController::close (:65–115), route POST insurance-policies/{id}/close (routes/api.php:225), loads policy/user/order, checks source policy.status == open and validates a required reason. A transaction sets closed_at and closure_reason and creates NewInsuranceActivity; afterward it queues this Mailable to policy.user.email with native internal BCC at :97. A separate SMS uses the order phone. Preserve closure/recipient/error ordering and do not alter SMS, policy/claim records or coverage dates through HTML.

NewInsurancePolicy derives status from closed_at/end_at: closed when closed_at exists, otherwise open/expired by date. The email uses that source close operation; it does not calculate status or invent an expiry email. build() binds policy/user. There is no explicit From/Reply-To override. Keep configured headers, subject, To/BCC and queue()/transport policy. Stored policy documents are not email attachments here.

Closure is not automatically cancellation, a refund, a claim decision or payout. The draft states only the existing policy ID and closure reason. Four fictional states cover normal closure, reason 0, supplied empty values as a view-only robustness check and long fields. Normal close validation still requires reason; the fixture does not create a valid empty-reason closure.

Integration and safety

Use the existing Mailable with a complete HTML Blade view and proposed plain-text alternative. Portable {{placeholder_name}} tokens are not automatically Blade variables. Escape names, policy IDs, reason text/attributes and validate image URLs separately; use one pass for string replacement. Preserve the optional-greeting and unconditional-reason distinction. No new framework or financial/insurance behavior is needed.

Files: public/ready/insurance-policy-closed.html/.txt and previews/insurance-policy-closed-*.html/.txt. No separate per-email project-owned text source found. No real policy/closure/message was created or sent. Browser checks do not verify actual policy/claim/coverage results, Gmail/Outlook/iOS Mail/CID behavior or production delivery.