BuiltEmail workspace

Invoice waybill contract

Implementation: App\Mail\WaybillMail (app/Mail/WaybillMail.php). Original body: resources/views/emails/invoice/waybill.blade.php, using mail::message and conditional mail::promotion. The Mailable uses envelope(), content() and attachments(), not build(). Static references establish usage evidence, not proof of current production delivery.

Suggested subject: Invoice #{{invoice_number}} Waybill (existing exact pattern).

Suggested preheader: Waybill for invoice {{invoice_number}} from {{business_name}}.

Read the shared-footer contract as well: app badge PNGs require real CID embedding or approved HTTPS hosting; the default shared button policy does not invent a body action for this email.

Portable placeholders

TokenExisting source / derivationHandling
{{logo_url}}Approved original Built app_name PNGOptional deployment override; concrete approved HTTPS logo already exists. Use approved HTTPS or a real matching CID.
{{preheader}}Suggested existing invoice/business summary aboveRequired escaped text. No dispatch, arrival or delivery claim.
{{invoice_number}}Public constructor $invoice->invoice_numberRequired escaped reference, not invoice UUID or numeric ID. Also used by the source subject and PDF filename.
{{business_name}}Public constructor $business->nameRequired escaped merchant identity and original Regards sign-off. Not the request-selected recipient or customer name.
{{merchant_message}}Public constructor $message, passed from request.message, converted into reviewed equivalent textOptional escaped multiline message. Preserve paragraphs, meaningful text and anchor destinations during normalization. Do not trust it as raw HTML or silently strip URLs. Visibility comes from the original source value before normalization, as below.

No waybill_url, tracking number, shipping/arrival date, amount, carrier, customer name or delivery-status value is supplied by the email body. They are not added as required placeholders. The PDF remains the detailed document.

Trigger, recipient and queue evidence

routes/api.php:883 POST send-invoice-waybill/{id}/email calls app/Http/Controllers/General/emailsController.php:536 sendInvoiceWaybill(). The controller validates email as required|email:dns and message as nullable|string, gets the current business, and finds the invoice by business_id plus uuid. A missing invoice returns Invoice not found rather than queuing mail. Preserve the existing business scope, UUID lookup, authorization and validation in the consuming application.

The controller passes $business, $invoice, request.email and request.message to InvoiceRepository::sendWaybill (app/Http/Repository/InvoiceRepository.php:1199). That method explicitly calls Mail::to($email)->queue(new WaybillMail(...)) at :1204, reports caught exceptions and returns its existing response. Recipient is the validated request-selected address; do not reroute it to customer.business_email, the owner or a driver. No additional CC/BCC is established by this path. Explicit queue() is the usage evidence; Queueable/SerializesModels alone do not establish delivery. Do not convert the queued path to send().

Headers and PDF

envelope() sets From to config(mail.from.address) with business.name, and the exact subject Invoice # + invoice_number + Waybill. Reply-To is set only when business.email is truthy, using the business email/name. There is no owner-email fallback in this Mailable. Keep the existing predicate and default behavior when absent; do not copy another receipt/report Mailable’s null-coalescing fallback into it.

attachments() keeps SnappyPdf::loadView(PDF.waybill, ['invoice' => invoice, 'business' => business]). Attach the existing PDF through Attachment::fromData with exact filename invoice_#{{invoice_number}}_waybill.pdf and MIME application/pdf. Preserve the lazy output callback, source view case and filename punctuation/case. Do not rename the file into a tracking number or replace it with the browser print/download route.

Retain every PDF-only dependency: items and quantities, customer/business details, invoice settings and merchant color, extra headers/footers, source note and its settings predicate, signature/endorsement and branding controls. This draft neither generates a sample PDF nor moves their computations/data into the email. Shared app-badge CID images are separate inline assets, not replacements for this PDF.

Optional message and rich-text handling

The original body uses @if($message) and raw {!! $message !!}. The portable draft uses escaped equivalent text in a complete marked note table. Capture the original PHP truthiness predicate before transforming the source string: null, empty string and literal string 0 omit the whole note; original <p>0</p> is truthy and its normalized text 0 must remain visible.

The fixtures call that source-only boolean merchant_message_visible. It is an adapter control value, not a new {{placeholder}} or printed field. Keep it independent of the normalized text. In a full Blade view, use an explicit waybill_message_visible flag to wrap the entire note table and an escaped waybill_message value inside it. Apply the same original predicate to the plain-text note section. Remove label/table/spacing together when absent, retaining invoice reference, merchant identity, PDF instruction and Regards sign-off.

Choose a reviewed equivalent-text adapter that retains paragraphs/lists and meaningful anchor label plus complete destination. Plain strip_tags can lose anchor URLs. Do not interpret arbitrary messages as HTML. If a future integration instead requires rich formatting, define a separate narrow sanitizer/allowlist and an equivalent text alternative explicitly; it is not automatically provided by this portable escaped-text field. The normalized reference fixture uses a local inert explanation URL, not a live route to deploy.

Laravel message-helper migration detail

Public constructor properties expose the source business/invoice/message to the current Markdown view. In the inspected installed framework, Illuminate/Mail/Mailable.php::buildMarkdownHtml merges original view data over mail-helper data; this preserves the current constructor message string in Markdown. It is not evidence that the current Markdown body is broken.

A complete regular HTML view receives the framework’s $message mail helper via Mailer.php::send. When replacing Markdown with the full document, explicitly map the constructor note into a nonreserved Blade name such as waybill_message (and its visibility flag). Keep $message available for native embed() calls for the shared PNG badges. Do not use the reserved helper as merchant text or try embed() on the constructor string. Framework source is rendering evidence, not another project-owned template in the catalog.

Portable {{merchant_message}} is an explicit alias for this existing constructor value; it is not automatically a Blade variable. Prefer the same Mailable’s native complete HTML view plus a separate text view and explicit mapped data. Do not nest the full document in mail::message or introduce another framework. Preserve envelope(), attachment generation and queue behavior during integration.

Links and safety

There is no body action destination in the email source. The adjacent send-invoice-waybill/{id}/sms route calls a separate SMS path; InvoiceRepository::sendWaybillAsSMS constructs app.short_url + /view-waybill/{invoice.uuid}. The browser preview handler loads invoice data for invoice_waybill_preview; print/download routes are likewise separate. Do not add a View waybill or Download PDF button merely because these routes exist. No legacy duplicate email link fields exist.

Escape all dynamic text/attributes, including the note, reference and business name. Validate any image URL override separately for approved absolute HTTPS host/path or a genuine attached CID. If string replacement is used, use one pass after text escaping and separate URL validation; inserted token-like strings remain literal text and never become raw markup. Required missing values need a deliberate adapter decision rather than an invented merchant/reference.

Files and limits

public/ready/invoice-waybill.html and .txt are portable extracts. public/previews/invoice-waybill-*.html/.txt cover a note, no note, original string 0, original rich-text zero normalized to 0, normalized reference note and long values. No separate project-owned plain-text file was found for the Markdown body; the supplied .txt is a proposed equivalent.

These files share the current centered Built footer and PNG asset policy in docs/integration/shared-footer.md. Browser reviews embed those images locally, while downloads preserve the CID integration contract. No actual message, PDF, shipping action or email is produced. Browser rendering does not establish Gmail/Outlook/iOS Mail, MIME attachment correctness or current production delivery. Open decisions are the target-project note adapter, explicit reserved-name mapping, source visibility predicate, asset embedding/hosting, text-view registration and client approval.