BuiltEmail workspace

New sign-in alert contract

Purpose: tell the account holder that a new device signed in and offer the existing password-reset action. Baseline: App\Mail\SendNewLoginNotification, resources/views/emails/SendNewLoginNotification.blade.php and AuthRepository::sendNotificationOnNewLogin, called from AuthController and authTwoController.

Suggested subject: New sign-in detected on your Built account.

Native source subject in SendNewLoginNotification::build() is New signin detected on your account. The suggestion is a copy proposal, not the current header. Preserve the native subject by default during integration; adopt different wording only as an explicit recorded decision. See the account/invoice readiness review for the complete integration packet.

Suggested preheader: A new device signed in to your Built account. Review the device and location details.

Portable placeholder contract

TokenExisting Laravel source / derivationRequirement
{{logo_url}}Deployment asset value for the existing Built app_name PNGOptional deployment override; default is the verified project HTTPS Built PNG. Replace its src with an approved URL or matching CID if needed.
{{preheader}}Suggested fixed preheader aboveRequired text; no timestamp or guarantee
{{first_name}}$user->first_nameRequired existing greeting value; escape text
{{device}}$device, constructor argument from the caller's user-agent/device valueRequired; long-value wrapping is provided
{{ip_address}}$IPAddress, retaining the existing capitalized PHP bindingRequired; display the value as text; do not rename the Laravel source variable
{{country}}$location->countryRequired; fallback is 'Unknown' in AuthRepository
{{country_code}}$location->countryCodeRequired; fallback is 'Unknown', retaining existing spelling/casing
{{city}}$location->cityRequired; current resolver may combine city and country capital; fallback is 'Unknown'
{{country_flag_url}}$location->country_flagOptional integration input; the portable extract omits the flag image by default. Add it only with a validated, approved image URL/CID.
{{reset_url}}$link in the Blade data; supplied from the Mailable's private $resetLink constructor fieldRequired; same validated URL in button and visible fallback

Recipient: caller's $email, passed to Mail::to($email). The Mailable is sent through send() in AuthRepository, with no ShouldQueue declaration. The configured bank_api transport can still add its own queued outbox. Keep runtime behavior unchanged.

States and content rules

URLs and legacy aliases

AuthRepository creates a PasswordReset record and sets config('app.url') + /confirmreset/ + encrypt(record.id). That entire generated URL becomes the constructor's resetLink, then the Blade data key link, and finally this extract's reset_url. These names refer to one destination, not three separate actions. Keep the same value in both href occurrences and the visible fallback text. No token/id should be fabricated in a view or preview.

Plain text and integration

ready/new-signin.txt is the portable text alternative. Each populated state has a matching .txt file. Preserve user, device, IP, country/code/city, the unrecognized-sign-in instruction and reset link. The flag is decorative context and can be omitted from plain text.

For Laravel integration, convert portable placeholders into current Blade expressions and add an optional image only inside @if($location->country_flag). Use attribute escaping after validating the URL against an approved image-host policy. Preserve the source PNG aspect ratio for the Built logo. Keep the same Mailable inputs and URL generation. See integration-notes.html for the shared image, escaping, provider and email-client notes.