Email workspaceManual invoice reminder contract
Implementation: App\Mail\SendManualInvoiceReminder. Source view: resources/views/emails/invoice/reminder.blade.php. Usage: app/Http/Controllers/Income/invoicesController.php:3599 (sendManualReminder). These static references are evidence, not proof of current production delivery. Shared original layout: mail::message; original optional message uses mail::promotion. The drafts use the existing design renderer's standalone table shell instead of nesting a full document inside Laravel Markdown wrappers.
Suggested subject: {{reminder_subject}} (existing caller-supplied subject, not a fixed reminder title)
Suggested preheader: A message about your invoice from the sending business (proposed neutral text; do not invent overdue status).
Portable placeholder contract
| Token | Existing source / derivation | Handling |
|---|---|---|
| {{logo_url}} | Original Built app_name PNG | Optional deployment override; concrete project HTTPS PNG is already present. Use approved absolute HTTPS or matching attached CID. |
| {{preheader}} | Suggested copy above, assembled from existing context | Required escaped text; no new application field or expiry. |
| {{reminder_subject}} | $this->subject public Mailable property <- request.subject | Required caller-authored subject; reject CR/LF for header use and escape separately for the visible heading. Do not replace it with a generic fixed subject. |
| {{reminder_message}} | $this->message public Mailable property <- request.message; existing view uses nl2br($message) as raw HTML | Required complete merchant message. Portable draft uses escaped multiline text; preserve instructions and all meaningful link destinations under the rich-text decision below. |
Content, states and integration decisions
- Audience: the validated request.email recipient, not necessarily the stored invoice customer email. Caller checks invoice membership in current business before explicitly queueing. Preserve request routing, sender business name, and Reply-To business email or owner-email fallback.
- Keep PDF.invoice generation, invoice items/taxes and attached Invoice_#number.pdf. No invoice_url is currently bound to the email view; no generic payment button, amount, due date, customer greeting or past-due statement is inserted. The additional attachment sentence accurately describes existing sending behavior.
- The Mailable creates a local $data array for the PDF but does not pass it into markdown(). The public subject/message are available; do not assume invoice/business locals are already view bindings. The portable names explicitly alias those existing public properties. The visible heading repeats the actual subject, preserving merchant context.
- States: ordinary message, message with a visible inert reference link, long subject/message. Preview body links are local explanations. A production merchant message might contain arbitrary destinations; the integration adapter must preserve allowed destinations safely, not insert the sample link or silently remove them.
- No API-owned automatic overdue email body was established here. InvoiceRepository::saveReminderInScheduler concerns an external scheduler path, which remains excluded from this batch.
Rich text, escaping and safe integration
Portable {{placeholder_name}} tokens are not automatically Laravel Blade syntax. Prefer a dedicated full Blade HTML view and text alternative mapped explicitly to the existing Mailable values. Use view()/text() as appropriate during later authorized integration; no new templating framework is required. Keep existing constructor data and PDF-only bindings even when the body does not duplicate them.
All dynamic values in the draft are escaped text, including multiline merchant content and numbered failure details. Preserve newline structure. For existing raw-HTML merchant messages, a deliberate adapter must either convert reviewed rich text to equivalent text, retaining paragraphs and meaningful URL destinations, or render a narrowly allowlisted sanitized rich-text section. Simple strip_tags is not sufficient to preserve anchor destinations. Do not treat untrusted text as raw HTML, remove instructions silently, or repeatedly substitute inserted strings. If portable string replacement is chosen, use one pass after escaping text and separately validating URL values.
Validate destinations against intended HTTPS hosts/paths and preserve complete existing links. Optional blocks must be removed as whole marked sections in both HTML and text, not left with blank headings or unresolved tokens. Button/fallback links share the same value where present; there are no additional legacy duplicate link fields in these four views. No button is invented for the manual reminder.
The concrete default logo is https://built-storage.ams3.cdn.digitaloceanspaces.com/Built/app_name.png. Retain assets/built-logo.png as the unchanged original. Use absolute HTTPS hosting or a real attachment with a matching CID for deployment; preserve the image aspect ratio. Remote images may be blocked. Native dark CSS is progressive enhancement and requires actual client testing.
Files and text alternatives
ready/invoice-reminder.html and ready/invoice-reminder.txt are standalone extracts. Each state has populated HTML and plain text under previews/. No separate project-owned plain-text alternative was found for these four Markdown views; the new .txt files are proposed equivalents. All preview identities and references are fictional; all application actions are inert local explanations. No email, form, booking, approval or payment is executed. See invoice-followups-batch.md and integration-notes.html for scope and shared requirements.
Full-view merchant-message alias
For a complete regular HTML view, bind the constructor message explicitly as reminder_message and preserve its full escaped/sanitized equivalent and plain-text version. Do not rely on the public message property as a full-view variable: Laravel uses $message for its native mail helper and CID embed() calls. Keep subject separate and preserve the original PDF-local data and header/attachment chain. This migration rule does not claim the existing Markdown rendering is broken.