Email workspaceEmployee payslip contract
Implementation: App\Mail\payslip. Baseline view: resources/views/emails/payroll/payslip.blade.php, using mail::message. Referenced callers: app/Http/Controllers/General/emailsController.php:444 (sendpayslip) and :463 (sendpayslips). PDF selection: app/Payrun.php:981, PayslipView(). Static references establish usage evidence, not production delivery.
Suggested subject: payslip for {{pay_period}} (existing exact lowercase pattern).
Suggested preheader: Your {{pay_period}} payslip from {{business_name}}. The PDF contains the details.
Placeholder contract
| Token | Existing source / derivation | Handling |
|---|---|---|
| {{logo_url}} | Original Built app_name PNG | Optional deployment override. Concrete approved HTTPS logo is already present; use approved absolute HTTPS or a matching attached CID. |
| {{preheader}} | Suggested text above, using existing period/business | Required escaped text; not a new payment or delivery guarantee. |
| {{first_name}} | $payslip->employee->first_name | Required escaped employee identity; not the business owner or payroll operator. |
| {{pay_period}} | date('F, Y', strtotime($payrun->run_date)) | Required existing formatted period. Also used in the original subject and PDF filename. Preserve formatting/timezone semantics; do not turn it into payday or a deadline. |
| {{net_salary}} | $business->currency->symbol . number_format($payslip->net_salary, 2) in the body view | Required formatted existing amount. Preserve currency prefix, two-decimal formatting, zero/negative values. Do not recompute deductions or use FX/base salary fields. |
| {{business_name}} | $data['business']->name, loaded from $payrun->business_id | Required escaped sign-off identity. This is not automatically payroll_settings.Company_name or the local employee-business object used later in build(). |
| {{portal_url}} | Existing literal https://payroll.built.africa/ | Conditional trusted HTTPS payroll-site root. Bind the same value to button and fallback when employee.email is truthy; otherwise remove the whole marked portal block and text counterpart. Do not change to /login, a document URL or a reset/setup endpoint. |
Source-only condition: $payslip->employee->email controls the portal section; it is not printed as a new email field. Source-only sender name: $data['payroll_settings']->Company_name from payrun-business payroll settings. These values remain in the existing Mailable/header contract; they are not invented required body tokens.
Recipients, headers, queue and attachments
- Single send: finds PayRunIitem with employee reimbursements/allowances/department, requires employee/email, trims email, then explicitly queues. Batch send: loads payrun items, iterates each employee with truthy email and explicitly queues the same Mailable. No ShouldQueue declaration exists on the Mailable; caller queue() is the evidence. Do not convert a batch into a shared-recipient email.
- Keep subject 'payslip for ' + formatted period. From uses configured mail address and payrun-business payroll_settings.Company_name. Reply-To uses a separately loaded employee-business email ?? its owner's email, with employee-business name. Preserve null-coalescing semantics and the owner lookup, not a newly invented generic payroll support address.
- The Mailable's data business comes from payrun.business_id, while its local $business comes from employee.business_id. Body currency/sign-off and payroll settings use the former; PDF country selection and Reply-To use the latter. Do not silently collapse these sources even if they usually agree.
- Keep SnappyPdf::loadView(Payrun::PayslipView(employee-business.country_id), $data) and Payslip_{pay_period}.pdf. Source mapping: GH -> payslips.payslip; NA -> payslips.payslipna; KE -> payslips.PayslipKE; unknown/missing/unmapped -> payslips.payslip. Preserve exact filename case on Linux.
- Retain all PDF data: payslip, employee, payrun, payrun business, payroll_settings and endorse (the existing Endosment lookup). Employee relation data and country-specific calculations stay in the existing PDF views. This HTML redesign does not change them or add their details to the email.
- The draft has no salary-transfer status, payment reference, gross pay, tax/deduction breakdown, bank account, password or retry URL. A generated payslip does not establish that a salary transfer completed.
States and conditional portal section
States: Ghana, Namibia, Kenya, default PDF mapping, zero net salary, long values, and no employee email. Country states share one email body and differ only in existing source data/attachment selection; they are not three new email types.
The standalone HTML includes a complete marked portal block. In Blade wrap it in the original employee.email predicate. When false, remove the button, its explanatory text and visible fallback together; remove the corresponding portal lines in text too. Keep the PDF instruction, period, net salary and sign-off. Current callers do not deliver to a no-email employee, so that fixture is explicitly template-only. It must not imply a new recipient routing path.
The primary label Open payroll portal improves the source's visible site reference. Explain that it opens the Built payroll site; do not label it Download payslip or imply a specific document page. No separate legacy duplicate link fields exist: the new button/fallback share the same original literal destination.
Native Laravel integration and safe values
Portable {{placeholder_name}} tokens are not automatically Blade variables. Prefer a dedicated complete HTML Blade view plus a separate text view using the existing Mailable data. Preserve existing headers, queueing, PDF generation/naming and URL destination. Do not nest this document inside mail::message or introduce another templating framework. Follow the repository's Laravel integration guide.
Use escaped expressions for names, period, formatted salary and attributes. Validate the payroll URL's HTTPS scheme/host/path separately from escaping. Preserve complete source values and do not insert untrusted raw HTML. If portable replacement is chosen, use one pass so inserted token-like text is not re-evaluated. Remove unsupported optional sections wholly, not by leaving unresolved placeholders.
Default logo: https://built-storage.ams3.cdn.digitaloceanspaces.com/Built/app_name.png. Retain assets/brand/built-logo.png as the original source. Deployment uses absolute approved HTTPS hosting or a real matching CID attachment, with alt text, proportions and white backing preserved. Relative fixture paths belong only to preview output. Remote images may be blocked; native dark CSS is progressive enhancement.
Files and testing limits
public/ready/employee-payslip.html and .txt are standalone extracts; public/previews/employee-payslip-*.html/.txt are fictional populated states. No separate project-owned text alternative was found for the Markdown source, so the supplied .txt is a proposed equivalent.
No real employee salary/recipient data, PDFs, email sends or portal actions are used here. Preview links are local explanations. Browser rendering does not establish Gmail, Outlook, iOS Mail compatibility, actual PDF correctness, CID delivery or successful portal authentication. See the payslip batch and validation records for evidence and open decisions.