Email workspaceNew 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
| Token | Existing Laravel source / derivation | Requirement |
|---|---|---|
| {{logo_url}} | Deployment asset value for the existing Built app_name PNG | Optional 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 above | Required text; no timestamp or guarantee |
| {{first_name}} | $user->first_name | Required existing greeting value; escape text |
| {{device}} | $device, constructor argument from the caller's user-agent/device value | Required; long-value wrapping is provided |
| {{ip_address}} | $IPAddress, retaining the existing capitalized PHP binding | Required; display the value as text; do not rename the Laravel source variable |
| {{country}} | $location->country | Required; fallback is 'Unknown' in AuthRepository |
| {{country_code}} | $location->countryCode | Required; fallback is 'Unknown', retaining existing spelling/casing |
| {{city}} | $location->city | Required; current resolver may combine city and country capital; fallback is 'Unknown' |
| {{country_flag_url}} | $location->country_flag | Optional 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 field | Required; 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
- Known location: show country, countryCode and city, plus flag if supplied. The sample uses fictional Ama Mensah, reserved documentation IP 192.0.2.84 and a local Ghana flag. No real login record was used.
- Unknown location: preserve the current resolver's 'Unknown' country/city/countryCode values and remove the flag image. Do not turn unknown into a confidently inferred location or omit device/IP context.
- Known location without flag: keep textual country/code/city; do not render a broken or empty image.
- Long values: the name, device identifier, example IPv6 and fallback URL wrap inside the email at 375px. The long reset URL remains a local design endpoint.
- The instruction remains to reset the password when the sign-in is not recognized. The action does not confirm a device, revoke sessions or approve a login.
- No login time is supplied. Do not add a timestamp token, a “just now” statement or an invented date.
- This alert does not state an expiry period in the existing baseline. Do not import the separate passwordReset email's five-minute copy into this alert without checking the actual workflow.
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.