Email workspaceBulk invoice creation results contract
Implementation: App\Mail\MultipleInvoicesCreatedMail. Source view: resources/views/emails/invoice/multiple.blade.php. Usage: app/Jobs/CreateMutipleInvoicesJob.php:98. 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: Invoice creation results (proposed neutral subject; current explicit source subject is Multiple Invoices Created!)
Suggested preheader: Invoice creation results for {{business_name}}.
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. |
| {{first_name}} | $firstName constructor property <- requesting user.first_name | Required escaped text. Preserve the source value. |
| {{business_name}} | $businessName constructor property <- business.name | Required available source value; originally bound but not printed in the email. Used to distinguish which business the results concern. |
| {{total_invoices}} | $totalInvoices <- count($this->customers) | Required supplied request count; preserve zero and do not substitute created count. |
| {{created_invoices}} | $createdInvoices counter incremented after successful repository response | Required supplied created count; creation is not proof of email delivery or payment. |
| {{failed_invoices}} | $failedInvoices counter incremented in exception catch | Required supplied failure count; controls the whole failure section. |
| {{failure_details}} | $failedMessages array, rendered as numbered plain-text entries joined with newline | Conditional required when failedInvoices > 0. Escape every entry as text; preserve each reason and customer context. Remove the entire marked table and corresponding text block when zero. Not a raw HTML token. |
| {{invoices_url}} | $url = https://app.built.africa/income/invoices | Required existing trusted HTTPS invoices-list destination. No account-specific or retry link is invented. |
Content, states and integration decisions
- Audience: requesting user after the bulk job; caller checks email format and explicitly queues. Preserve existing counters, per-customer catch behavior, websocket handling and queue lifecycle. This design does not retry failed requests, roll back created invoices, or send customer invoices.
- Show all counts for all states, including full success, partial failure, zero created/all failed, and long failure details. A neutral result heading and proposed neutral subject avoid a false success claim when no invoice was created. Changing the actual subject requires later integration; application code is unchanged.
- Source failedMessages embeds customer.business_name and exception.getMessage(). Do not output it raw. A production integration needs an explicit review/redaction policy for credentials, tokens, internal exception details and personal data before rendering. Preserve meaningful user-facing reasons and each entry; any redaction must be deliberate, not silent truncation or a generic replacement.
- There is no attached PDF or retry URL in this Mailable. View invoices opens the existing list. The supplied counts are not treated as email delivery counts or payment outcomes. No fake all-created sentence appears in a failure state.
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/bulk-invoice-results.html and ready/bulk-invoice-results.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.