BuiltEmail workspace

Built email redesign integration notes

Insurance claims — current scope complete

[Claim receipt](notes/insurance-claim-submitted.md) and [claim status update](notes/insurance-claim-status.md) add 14 fictional states. Current library: **67 types / 270 states**, in 18 categories. See the [batch record](../batches/insurance-claims-batch.md) and [validation](../validation/insurance-claims.md).

Receipt uses constructor policy.policy_id and queues the same body for the claimant/internal copies. Its user greeting is not explicitly bound. Status uses claim.user, original previous/current statuses and reason only when cancelled; it supplies no policy ID, attachment, action or payout assurance. Customer cancel/update endpoints do not send this Mailable; admin changeStatus does. Preserve native recipients, subjects, queue semantics and separate SMS behavior.

No current-scope non-CRM types remain. Five API-owned CRM notices stay deferred. Next quick win is category-by-category integration readiness, beginning with accounts and then invoices, before target-app implementation or actual email-client testing. The sections below retain historical batch totals and decisions.

Insurance orders and policies

All five drafts are implemented: [insurance-order-received](notes/insurance-order-received.md), [insurance-provider-purchase](notes/insurance-provider-purchase.md), [insurance-order-status](notes/insurance-order-status.md), [insurance-policy-closed](notes/insurance-policy-closed.md), [insurance-policy-created](notes/insurance-policy-created.md). At completion of that batch: **65 types / 256 states**, in 17 categories. See the [batch record](../batches/insurance-orders-batch.md).

Preserve customer/internal copies versus provider recipients, queue versus native send, motor/life branches, uncapped motor rows, cancellation reason and completed-policy fallback. No workflow URL or email attachment is supplied by these five. The order-received greeting is not bound explicitly; an optional greeting does not silently infer order.user. Provider raw amounts, received two-decimal amounts and the opaque zero-decimal Curasel description remain distinct. Curasel has an unresolved optional-order guard and uses the older policy model; layout does not resolve those application concerns.

Both claim emails are now complete; see the current section above.

Internal payment operations

All three internal payment-operation drafts are implemented: [bank top-up authorization](notes/bank-topup-authorization.md), [transfer review / retry](notes/transfer-failure.md) and [direct bill-payment review](notes/bill-transfer-failure.md). Library totals: **60 types / 227 states** in 16 categories. See the [batch record](../batches/payment-operations-batch.md).

Top-up code comes from transaction.secrete, which is hidden in serialization and cleared in memory after native send/SMS. Preserve exact code/case, destination-bank fields and native internal recipient without adding expiry, approval link or verified receipt/credit. Keep the code out of subject/preheader.

PaymentError preserves the original-link invoice prerequisite separately from usable retry validation. Null/0 links omit it; # retains the prerequisite but gets no working-looking Retry button. Its helper has non-production diagnostics and an unfiltered callback reference, so a neutral review heading does not assert verified failure for every reason/status. Keep source subjects, recipient/queue policy, amounts and URL tuples unchanged.

Bill-payment failure is direct Mail::send of mails.payment_limit, not the apparently unused PaymentLimit Mailable. Preserve two caller subjects, native literal bill reference, provider message and reported collection context without claiming independently verified settlement. Existing Paystack dashboard navigation remains distinct from retry. Neither body changes accounting/paid-status writes or provider actions.

Historical batch total; both insurance claim notices are now complete. Five API-owned CRM notices remain deferred separately.

Loans and financing requests

All four referenced financing drafts are implemented: [provider application notice](notes/financing-provider-request.md), [applicant acknowledgement](notes/financing-application-confirmation.md), [invoice-discounting request](notes/invoice-discount-request.md) and [overdraft request](notes/overdraft-request.md). Library totals: **57 types and 210 states**, with sidebar category Loans & financing. See the [batch record](../batches/financing-batch.md).

Provider/applicant emails preserve queue(), To/CC/BCC and their distinct sender/Reply-To data. Service ID 7 retains Fido app-download instructions and destination; generic acknowledgement has no workflow button. Submitted never becomes approved. Discount/overdraft preserve internal recipients, native send(), business-owner Reply-To, original amount/currency semantics and independent optional wallet/file/review sections. No business PDF is attached by these four Mailables.

Keep discount note as reviewed escaped text with meaningful link destinations; original raw HTML is not a trusted contract. Check original attachment path before prefixing config(app.url), and Wallet object existence independently of zero balance. Overdraft uses the supplied encrypted /printbill/ link, not request_details.print_link; its original subject explicitly includes business/bill despite the earlier scanner omission. Supplied interest, total, period and repayment date remain unchanged values.

Next full category: internal payment operations, three notices. Ten in-scope email types remain across that group and the two insurance groups; deferred CRM notices stay separate.

Affiliate account confirmation and invitation

The library now contains **53 notification types and 190 states**. New sidebar category: Affiliates & referrals. See the [batch record](../batches/affiliate-batch.md), [account confirmation contract](notes/affiliate-account-created.md) and [invitation contract](notes/affiliate-invitation.md). Both are referenced API-owned emails; unused CustomerInvite/SupplierInvite remain deferred.

The confirmation provides a copy/share referral URL and explicitly warns the affiliate not to sign up through it themselves. There is no signup button. The invitation uses the same source host/code as a signup action, preserves the original one-month Enterprise offer and ten feature categories, and keeps two distinct informational website destinations. Duplicate signup buttons become one action plus fallback. No commission rate, deadline or instant settlement promise is added. Current offer redemption/eligibility is external to this Mailable and remains an integration check.

Keep confirmation send() to validated request.email after account creation, versus invitation queue() to request.email before the tracking record is created. Preserve native/configured headers and account-name/code identity. A complete HTML Blade view and proposed text alternative fit the existing Mailables; no new framework or API behavior is introduced.

Next quick win: internal bank top-up authorization. Trace transfer-failure notices separately because their internal error/retry contexts vary.

Six wallet, deposit, card and token emails

The library now contains **51 notification types and 184 fictional states**. New drafts are under Payments & wallet: [deposit transaction receipt](notes/deposit-transaction-receipt.md), [wallet welcome](notes/wallet-welcome.md), [invoice payment received](notes/invoice-payment-received.md), [virtual card created](notes/virtual-card-created.md), [card transaction alert](notes/card-transaction.md) and [utility token received](notes/utility-token.md). See the [batch record](../batches/wallet-six-batch.md).

Deposit summaries preserve debit/credit, padded ID, creation timestamp, business currency, null-only description fallback and the existing Snappy PDF. Merchant invoice-payment notices use the final singular charge argument and amount minus that charge, with original body support contacts; they are not customer PDF receipts. Card-created balance remains raw, and card-debit amounts have already been divided by 100 at the webhook before the email. Only masked card numbers belong in either body.

Wallet welcome keeps pending-approval wording but removes an unsupported review-timing claim. The source sends that same body even when non-production signup stores approved; this needs a separate application decision. The utility email retains fixed KES, original PIN/units/reference and leading zeros, while SMS separately uses wallet currency. Do not silently resolve those source differences through HTML.

Desktop categories now expand one at a time, with only the first open on a clean visit and the selected category revealed for explicit email links. Mobile keeps its searchable category picker. Navigation and theme controls remain outside portable emails. Next quick wins: affiliate account confirmation and invitation, then the internal bank top-up authorization notice.

Payment request and payment-link receipt

These drafts bring the library to **45 notification types and 160 states**. See the [batch record](../batches/payment-link-batch.md), [request contract](notes/payment-request.md) and [payer receipt contract](notes/payment-link-receipt.md). Sidebar category: Payments & wallet.

Payment request preserves link title, wallet-labelled merchant identity, wallet.currency + stored link.amount, optional escaped description and complete short_link. Open payment link opens that existing payment page, with a shared-value fallback; the unused /viewinvoice/ variable does not create an invoice-review action. Keep description PHP truthiness and do not infer expiry, a fixed checkout charge or zero-price entitlement from stored amount/settings.

The payer receipt preserves transaction.id, wallet label, wallet.currency + transaction.amount and created_at formatted d-M-Y. Transaction date replaces the old Payment Date label to describe that creation-date source accurately. No business PDF or body URL is supplied by either source Mailable. Collection provider branches are context/states, not separate templates or new status claims.

Keep each native send() caller and recipient policy: comma-split request emails for the request, transaction.customer_email for the processed-collection receipt. Header From uses Business.name while body/subject use wallet.business_name. Request Reply-To falls back to business.owner_id; receipt Reply-To instead uses wallet.user_id and supplies no display name. Preserve source processing/top-up/one-time rules and the receipt view spelling paynentLinkReciept.

Next quick wins: deposit-transaction receipt and wallet welcome.

CFO AI welcome and management-report sharing

These drafts bring the library to **43 notification types and 149 states**. See the [batch record](../batches/management-report-batch.md), [welcome contract](notes/cfo-ai-welcome.md) and [sharing contract](notes/management-report-sharing.md). All four report-related emails are grouped under Reports & CFO AI in the workspace.

CFO AI welcome preserves the existing one-free-report message, supported by the first-settings tokens = 1 allocation, and both generation/download-sharing capabilities. It opens the existing CFO AI site root. The source caller sends synchronously only after creating settings; existing settings return without repeat mail. The draft removes the unsupported few-seconds timing claim and adds no balance, trial/expiry or recipient-name field.

Management-report sharing preserves request-selected To/CC, ownership/default-report rules, queued delivery, report.name subject and report-user From/Reply-To. The default body has report business/name, d-m-Y dates and report-user sign-off. Nonempty custom body replaces all that body context; keep the original PHP !empty predicate separate from normalized text so rich-text 0 is not dropped. A factual PDF reminder is present in either branch, with no invented dashboard action.

Keep restructurePages and the cfo-ai.template Browsershot A3 landscape PDF path, same-model mutations, user dependency and two Str::slug filename components. Constructor businessName is unused by the source view and must not overwrite report.business_name. Source bodies remain API-owned even when accounting data is external.

Next quick wins: payment request and payment-link receipt, tracing payer/merchant audiences and external checkout destinations first.

Invoice waybill

The invoice-waybill draft brings the library to **41 notification types and 140 states**. See the [batch record](../batches/waybill-batch.md) and [contract](notes/invoice-waybill.md). It retains invoice number, business identity, optional merchant note, PDF instruction and Regards sign-off, with the current shared footer. No body action URL exists; the waybill web link belongs to a separate SMS path.

The controller validates request email/message and scopes the invoice by current business plus UUID. The repository explicitly queues to the supplied recipient. Preserve the exact Invoice #number Waybill subject, conditional business-email Reply-To without an owner fallback, and SnappyPdf PDF.waybill attachment with invoice_#number_waybill.pdf and application/pdf MIME.

Capture the original message’s PHP truthiness before equivalent-text normalization. Literal 0 is hidden, but original <p>0</p> remains a visible 0 through its separate flag. Preserve paragraphs and meaningful link destinations; escape the normalized text. When adopting a complete Blade view, explicitly map the constructor note to a nonreserved variable and keep Laravel’s $message helper available for badge embedding. The existing Markdown merge behavior is documented; no current production bug is asserted or changed.

Next quick wins: CFO AI welcome and management-report sharing.

Shared footer and action sizing

All 45 email types now share the user-approved footer and medium action policy. Read the [shared footer, assets and variants contract](shared-footer.md) in addition to the selected-email contract. Footer data comes from the supplied reference, corroborated by the API’s shared footer. The older welcome address format conflicts; it is documented and not silently substituted.

Supplied WebPs are retained and converted into pixel-identical PNGs. Ready extracts use real CID references; embed the supplied files or replace those src values with approved absolute HTTPS URLs. Browser reviews embed local assets; downloads retain this attachment contract. The asset manifest and release hashes include the integration files. Welcome app URL aliases remain, now on the shared badges, and map to the user-supplied exact app destinations.

Medium buttons are 40px on desktop with 44px mobile targets. Change the shared default in src/emails/email-design.cjs and rebuild to update all actions; sm/md/lg and per-render buttonSize overrides are documented. This replaces the earlier large default without changing transactional action destinations or copy.

Invoice payment and sales receipts

Two receipt drafts bring the library to **40 notification types and 134 states**. See the [batch record](../batches/receipt-batch.md), [invoice receipt contract](notes/invoice-receipt.md) and [sales receipt contract](notes/sales-receipt.md).

Invoice receipt keeps invoice.fx_currency + formatted invoice.amount_paid, the cumulative recorded invoice total, separate from the associated payment method/date and unused-in-body constructor amount. The invoice’s independent optional phone/address/email rows remain conditional. Keep all four callers’ send-flag predicates and customer-business-email routing.

Sales receipt keeps business.currency.symbol + formatted sale.amount_due, raw entry_date and business sign-off. Its neutral Receipt amount label avoids treating the old Amount Paid wording as proof of settlement. Do not replace this value with the PDF’s distinct amount_due + balance expression. The recipient comes from the validated send request.

Both keep existing PDFs, exact subjects/filenames, From/Reply-To behavior, receipt-page links and one shared button/fallback URL. Preserve /invoicereciept/{invoice.uuid} and /viewreceipt/{sale.enc_id}; no path correction, checkout or new payment endpoint is introduced. Amount-policy changes remain separate integration decisions.

Next quick win: invoice waybill cover, then assess management-report onboarding/sharing notices.

Payroll report covers

Four payroll report drafts bring the library to **38 notification types and 123 states**. See the [batch record](../batches/payroll-report-batch.md) and per-email contracts for social-security-report, tier-two-report, tier-three-report and income-tax-report. Existing callers explicitly queue to request.email; no employee or statutory-authority recipient is inferred.

Social security delivers an XLSX spreadsheet through SSNITExport. Tier 2/3 deliver PDFs through their existing distinct renderers/views. Tax keeps its country-selected A4-landscape PDF. Preserve reporting month, configured scheme label, employer, address/telephone, original sign-off identities, From/Reply-To fallback semantics and filename quirks. No new action URL exists. Tier 3 keeps config(app.name); the tax body/header payroll-settings sources stay distinct.

Contact rows are optional complete blocks; remove the whole table and text lines when both values are absent. Tier 2/3 gain accurate attachment instructions. These designs add no amounts, rates, filing/payment claims or deadlines. Legacy direct-job bodies remain separate unresolved-dispatch surfaces.

Next quick wins: invoice payment receipt and sales-receipt covers, tracing their payment data, recipients, links and attachment contracts first.

Employee payslip

The payslip draft brings the library to 34 notification types and 105 states. See the payslip contract and batch record. Preserve employee first name, the original Month, Year formatting, business-currency net salary, PDF instruction and business sign-off. The conditional portal button/fallback use the existing https://payroll.built.africa/ root, not onboarding's /login or a new download route.

PDF selection uses the employee business country (GH/NA/KE/default), while body business/currency and sender payroll settings come from the payrun business. Keep these sources and exact PDF view case distinct. Current callers explicitly queue only for employees with an email; the no-email fixture is template-only and removes the whole portal section in both HTML/text. A payslip does not establish a completed salary transfer.

Next quick wins: statutory payroll report cover emails. Trace recipients, periods, PDF selection and instructions without altering statutory computations.

Employee payroll-portal onboarding

Employee onboarding brings the workspace to 33 types and 98 states. See employee-onboarding-batch.md and notes/employee-welcome.html. The one notification type retains first name, payroll employer name, login email, supplied password and payroll.built.africa/login. A single Open payroll portal button and visible fallback use the same source URL.

The direct controller/importer paths explicitly queue welcomeEmployee. The bulk import job renders that same API-owned body before handing email/content pairs to external bulk delivery. Keep transport orchestration out of scope; the body stays API-owned. Bulk subjects/reply_to/from and text alternatives need explicit provider handling rather than assuming an HTML edit updates headers/payloads.

Keep payroll_settings.Company_name distinct from the business.name subject/From identity. Password is the supplied constructor value, not the stored bcrypt hash; preserve it as escaped string data without trimming, numeric conversion, masking or regeneration. The source establishes no temporary/one-use/expiry/first-login-change behavior. Previews use synthetic demo-only values and example.com addresses. Replacing password delivery needs a separate authorized application workflow.

Next quick win: employee payslip email, before statutory payroll reports. Trace payslip/payrun content, recipient, portal destination and PDF attachment.

Subscription notices

Four subscription notices bring the workspace to 32 types and 94 states. See subscriptions-batch.md and the four selected-email contracts. Confirmation preserves original cycle and conditional amount/tax displays plus its subscription_invoice.pdf attachment. Failed attempts keep the escaped reason and support instructions, removing the unusable empty button. Cancellation retains plan, source start/end values and the source free-basic downgrade statement.

FailedSubscription is an internal undefined-subscription-type collection alert inside the code 00 success branch, not an established failed customer debit. Its draft uses a neutral review heading and proposed subject, retaining business ID/name, raw amount, network and transaction_id as Cycle reference. Keep it internal and preserve synchronous sending.

The confirmation body tests plan.cycle while AppBilling populates subscription_cycle, and its tax predicate uses country.currency_symbol while the PDF uses country.code. These legacy differences and percentages are mapped explicitly, not silently corrected or endorsed as current tax law. Remove the complete optional tax block when its source condition is false. Failed-attempt support email is proposed as support@built.africa to correct the source typo. Cancellation provider cleanup and free-basic entitlement need deliberate integration review; no refund or charge-stop guarantee is added.

Next quick win: employee payroll-portal onboarding, before payslips and statutory payroll reports. Subscription expiry remains deferred as apparently unused.

Customer and supplier statements

Both transaction statements are now drafted, bringing the workspace to 28 types and 80 states. See statements-batch.md and notes/customer-statement.html / notes/supplier-statement.html. Each preserves its original four summary values, date-range/all-transactions condition, render-time As at value, first ten entries in source order, optional message, business sign-off and PDF instruction. There is no source action URL.

Ready HTML contains a marked scoped entry-table prototype. Repeat it for the first ten transactions with escaped per-entry values; remove it when empty. This is not a raw transactions_html token. The matching text file marks the equivalent repeat block. Full collection and PDF generation remain unchanged; do not infer summary values by totaling the ten displayed entries.

Customer total_owing remains the overall customer balance, not range totals minus paid. Customer per-row FX mapping, supplier business-currency rows, and differing PDF mappings remain documented. The customer date lookup uses getContent rather than the supplier get; range previews establish the view contract, not runtime endpoint correctness. Supplier notes are template-supported but currently always empty at the caller. The all-transactions supplier subject has a legacy empty from/to issue that is exposed rather than silently fixed.

Next quick wins: four referenced subscription billing notices (confirmation, failed attempt, automatic payment failure and cancellation/downgrade), followed by employee onboarding. SubscriptionWillExpire is apparently unused and remains deferred. Trace exact financial/period states before implementation.

Quotes and invoice follow-ups

Four more API-owned types are ready for review: quote, manual invoice reminder, invoice link visited, and bulk invoice creation results. See invoice-followups-batch.md and each selected contract. The workspace now has 26 types and 67 states.

Quotes retain the existing quote link, source validity date, optional merchant message and attached PDF. Manual reminders keep the merchant-authored subject/message and PDF; no payment link or overdue status is invented. Their current raw HTML message handling needs deliberate safe rich-text conversion or sanitization at integration, preserving instructions and link destinations.

Invoice visits retain every original field and calculation. The original Carbon::now value is render time, labeled Notification time, not a persisted visit timestamp. The legacy business.phone_name is explicitly mapped, with an em-dash fallback rather than a silent phone_number substitution. A link visit does not prove customer readership.

Bulk results support full success, partial failure and all failed. The proposed subject Invoice creation results is neutral; the source header remains Multiple Invoices Created! until later authorized integration. Keep all counts and each failure reason, with escaped text and a deliberate secret/PII review for exception-derived content. Remove the complete optional failure section when no requests failed. No real exception data is included in fixtures.

Virtual accountant support and internal operations

Five additional types are ready for design review: virtual-accountant plan acknowledgement, virtual-support onboarding, internal accountant pairing, internal wallet signup and internal developer signup. See support-operations-batch.md and each selected email's contract for the full existing-data mapping. The workspace now has 22 types and 52 populated states; all earlier drafts remain.

The support welcome currently greets the business name passed as customerName. Preserve the separate support site, Google Forms checklist and Cal.com booking destinations; booking is the single primary action. Keep service CC/Reply-To handling in the existing Mailable. The acknowledgement has no supplied action URL and no invented deadline.

Internal pairing contains business/user contacts and separate creation values. Keep this internal audience, preserve the conditional invited-accountant notice, and remove its complete marked paragraph and text counterpart when has_accountant is false. Optional contact values use an explicit em-dash fallback. Wallet and developer notices are internal signup alerts, not customer approvals. Developer links to the list, and its pending-status caller can run more than once. Preserve current queue and routing behavior. The acknowledgement and pairing subjects are proposed header choices because their source Mailables have no explicit subject.

Team, report and product-import batch

Ten new types are added: six team/access notices, report completion/failure and product-import completion/failure. See easy-wins-batch.md for every HTML/contract path and easy-wins-validation.md for checks. Each has a matching plain-text alternative and populated states. CRM imports/external campaigns and unused legacy copies stay excluded.

The source supplies no URLs for accepted-invitation, role-change, payroll-access, report-failure or product-import notices, so those drafts have no buttons. Only invitations and completed reports use existing actions. Accountant invitation adds an explicit button for its existing visible link; report completion names the CFO AI destination and retains its PDF instruction. No expiry, roles, reasons, deadlines, processing guarantees or retry URLs are invented.

Import completion/failure share InfoMail, which currently binds a title and prebuilt HTML body rather than structured fields. The new contracts map first name, business and three counts to the existing ItemRepository callback values. Integration must pass them deliberately into a dedicated/typed view while preserving the old body's information; do not assume the generic wrapper already exposes them or parse untrusted HTML to reconstruct data. Failure fallbacks remain documented, and success must not receive invented zero fallbacks.

Invitation accepted and payroll revoked have proposed subjects because their Mailables do not explicitly set one. Other new subject patterns preserve the current explicit source subjects. All sender/reply-to, queue and attachment behavior remains an integration responsibility; no application code is changed here.

This is a design draft. No Laravel view, Mailable, attachment path, mail transport or application behavior has been changed. All 34 email drafts use the user-approved Built navy #0D2E57 and yellow #F9E251. Merchant invoice_color remains a documented source value for a later secondary treatment; it is deliberately unused in this draft's styling.

Files and recommended approach

The portable {{placeholder_name}} tokens are a delivery contract, not runnable Laravel Blade. In Blade, {{placeholder_name}} does not reference a PHP variable; Laravel would normally require {{ $variable }}. Do not copy these extracts into a Blade view and assume their tokens resolve.

After design approval, the best fit is a dedicated Blade view per notification using its current Mailable data. Convert each token to its mapped Blade expression and each optional section to @if/@isset. Keep the existing Mailable constructors, queues, attachments, sender/reply-to behavior and URL generation. Use view() for a full HTML document and a separate text() view where appropriate; nesting a whole HTML document inside mail::message would duplicate wrappers. This is an integration recommendation only, not an application change in this folder. Do not introduce another templating framework.

Safe values and optional sections

Escape dynamic text and attribute values. The generator uses HTML escaping for &, <, >, quotes and apostrophes; merchant_message is text with preserved newlines, not raw HTML. Existing $message can contain HTML. Before integration, decide whether to convert it to text while retaining paragraph breaks and link destinations, or supply a separately sanitized Blade section with an explicit allowlist. Never put untrusted text into raw {!! !!} output, and never silently drop its instructions or URLs.

Validate URL values separately from HTML escaping. Require absolute HTTPS application URLs or approved short-link hosts for account/invoice actions; reject credentials, control characters, javascript/data/file schemes and unexpected hosts. Keep the existing complete generated URL, including signed or encrypted parameters. Do not regenerate URLs from an identifier in the template. For logo/flag images, allow only approved HTTPS image hosts or a CID created by the actual mail attachment workflow. Attribute-escape the validated URL afterward. The design renderer accepts HTTPS/CID plus narrowly scoped local paths in preview mode; it is not a production host-allowlist validator.

The ready HTML includes comments around the optional merchant-message section. The flag image is omitted from the portable extract by default; an integration comment documents its optional country_flag_url input. They are human-readable integration markers. They are not conditionals. Remove the entire section when absent before sending, or convert the markers to Blade conditions. Do not send empty image URLs, unresolved tokens, blank merchant-note headings or empty security detail labels. Do not treat a placeholder token as raw HTML for inserting a section. All ready files need data population before they can be sent.

The build code does no string replacement of untrusted values into a pre-rendered document; renderEmail reads each value once at its output position and escapes it there. If a future adapter uses token substitution, use a single replacement pass, text-escape values and validate URLs independently. Repeated replacements can interpret token-like text inside user values as new instructions.

Images and client compatibility

Use the provided PNG wordmark at its natural aspect ratio (907 × 258 source pixels): 112px content width for welcome/invoice and 96px for compact security emails. Do not reuse BeautyMail's 400 × 200 sizing. Both ready HTML files now include the existing project HTTPS logo URL by default: https://built-storage.ams3.cdn.digitaloceanspaces.com/Built/app_name.png (verified HTTP 200, image/png). The optional logo_url integration override can replace that src with an approved absolute HTTPS image URL or cid:content-id with the matching PNG attached inline by the mailer. The default public URL still requires an image download and can be blocked by an email client. The local ../assets paths are for preview documents only. Retain the local original as source; it is not a deployed email URL.

The templates use tables, inline critical styles, system-font fallbacks, responsive media queries and an MSO fixed-width outer wrapper. Rounded corners and optional cosmetic styling may degrade gracefully. The sendable documents contain no scripts, forms, flex/grid layout, remote stylesheet, preview controls or tracking pixel. The preview workspace may use browser CSS/JavaScript because it is not sendable HTML.

The current bank_api mapper prefers one HTML body, falls back to text and drops CC, BCC and Reply-To. Adding a Laravel text view will not by itself make that provider send multipart mail. Confirm provider support separately. Do not remove current attachments or change acceptance-status logic to make a design preview work. No real emails should be sent as part of this review.

Confirmed copy and visual changes

Open integration decisions

Dark appearance

Both email extracts declare light/dark color-scheme support and include prefers-color-scheme: dark media rules. The dark palette uses a deep blue canvas, lighter blue card, readable pale text and yellow primary action with navy text. The original navy/yellow PNG remains on a small white backing so its wordmark stays legible without altering the source asset. Light inline styles remain the fallback in clients that ignore the media query.

The workspace Appearance selector offers Light, Dark and System. The selected mode is preserved in the URL and applies to the embedded email and Open preview document. It forces the existing email media rules for review without adding scripts or controls to the sendable file. Standalone ready files follow the browser/client preference. Browser simulation does not predict forced color inversion by Gmail, Outlook or other mail clients; client testing is still required.

Account batch content decisions

The six referenced account/auth notifications are now available in the workspace; the invoice remains separately accessible. The five new drafts have HTML/text extracts, two populated states each, native light/dark support and per-email contracts. Account navigation is grouped first and defaults to signup welcome. External CRM campaigns, team invitations and legacy duplicate bodies are not added to this batch.

Welcome preserves the source feature list, dashboard overview topics, dashboard destination, Built website and both store destinations. Decorative feature tiles and store badges are replaced with readable text and accurately labeled secondary links. Welcome to Built is a proposed subject; GetStarted currently derives its subject from the Mailable class. Other new proposed subjects preserve their current explicit subject strings.

Verification retains the first name and existing /verifyaccount/{encrypted user id} destination, with an added visible fallback URL. No expiry is invented. Password reset retains all three steps, one-use/five-minute copy, unsolicited-request instruction and fallback. The code email retains the code as a string and the existing five-minute statement; it adds an instruction not to share it, with no fabricated button or single-use claim. Disabling two-factor authentication retains the exact action, one-use/five-minute copy and destination, with an added fallback and instruction not to use an unrequested change link.

The code sample 004827 tests preservation of leading zeros. Long-name/link fixtures exercise wrapping rather than new backend states. Email and Google signup examples share their existing welcome body; no additional verification sequence is inferred. Expired/completed security emails are not invented.

Approved shared refinement

The user selected dashboard activation as the welcome’s main job, a strictly transactional voice/footer for security messages, and compact security layouts. Welcome keeps its data/destinations and product capabilities, with the dashboard action before supporting education. No source feature or URL has been silently dropped.

Verification, reset, code, disable-two-factor and new-device messages now share compact spacing: a 96px logo, 20px logo-to-heading space, 14px paragraph spacing and tighter card padding. Body text remains 16px/25px; fallback instructions and sign-in labels use 14px/22px. Buttons retain their existing padding and minimum practical tap size. Security footers show Built only, without the product tagline or friendly team sign-off. The original PNG remains on a white backing in dark mode because no approved reversed asset was supplied.

Reset’s action and one-use/five-minute notice are grouped before the preserved three steps. Verification/sign-in still have no invented expiry; code still preserves leading zeros and has no invented action. HTML and text alternatives follow the same content order. No production email behavior is changed.