Account and security
New device login alert: template and code
Tell people right away when their account is used from a new device.
Passing, tested 2026-10-19 · Email, Inbox
How it works
When to send a new device login alert
A new device login alert warns users when their account signs in from a device you haven't seen before. It sends an email and an Inbox message at the same time, showing the device, location, and time, with a button to secure the account. Each new sign-in sends one alert, so there's nothing to stop.
Messages
What it sends
When an account signs in from a new device
Email and Inbox.
Your Acme account was used to sign in from a new device.
Device: Chrome on macOS
Location: Austin, Texas, US
Time: Oct 15, 2026, 10:42 AM EDT
If this was you, there's nothing to do.
In-app Inbox
Austin, Texas, US, Oct 15, 2026, 10:42 AM EDT. If this wasn't you, secure your account.
Other subject lines: "New sign-in from Chrome on macOS", "Was this you? New sign-in to Acme"
Build
Add it to your app
1. Create the template
Run setup.ts once with your API key. It creates the template in your workspace, publishes it, and prints the template ID you pass to step 2. Copy setup.ts and the templates folder from the full definition at the end of this section.
Terminal
COURIER_API_KEY=pk_... node --experimental-strip-types setup.ts
2. Send it when a new device signs in
Node.js
// Alert someone by email and in your app's Inbox when their account is used// from a new device, with the template ID setup.ts printed. Routing method// "all" sends on every channel in the list instead of picking one. The templates// format signedInAt as a date and time.import Courier from "@trycourier/courier";const client = new Courier();export async function sendNewDeviceAlert(templateId: string,userId: string,device: string,location: string,signedInAt: Date) {const { requestId } = await client.send.message({message: {to: { user_id: userId },template: templateId,data: {device,location,signed_in_at: signedInAt.toISOString(),},routing: { method: "all", channels: ["email", "inbox"] },},});return requestId;}
Python
# Alert someone by email and in your app's Inbox when their account is used# from a new device, with the template ID setup.ts printed. Routing method# "all" sends on every channel in the list instead of picking one. The templates# format signed_in_at as a date and time.from datetime import datetimefrom courier import Courierclient = Courier()def send_new_device_alert(template_id: str,user_id: str,device: str,location: str,signed_in_at: datetime,) -> str:response = client.send.message(message={"to": {"user_id": user_id},"template": template_id,"data": {"device": device,"location": location,"signed_in_at": signed_in_at.isoformat(),},"routing": {"method": "all", "channels": ["email", "inbox"]},},)return response.request_id
Full definition: setup.ts, and the 1 template
One template with an email version and an Inbox version. The send call's routing method is all, so both go out at the same time. Your app decides what counts as a new device and passes in the details.
Replace the product name (Acme) and the Secure your account link once. Pass signed_in_at as an ISO timestamp: the time shows in each recipient's timezone from their profile, and New York when it isn't set.
setup.ts
// Creates this recipe's templates in your workspace from the templates/// folder and publishes them. Run it once per workspace://// COURIER_API_KEY=pk_... node --experimental-strip-types setup.ts//// It prints the template IDs that start.ts needs.import Courier from "@trycourier/courier";import { readFileSync, readdirSync } from "node:fs";const client = new Courier();const here = new URL(".", import.meta.url);const templateIds: Record<string, string> = {};for (const file of readdirSync(new URL("templates/", here))) {const body = JSON.parse(readFileSync(new URL(`templates/${file}`, here), "utf8"));const template = await client.notifications.create(body);templateIds[file.replace(/\.json$/, "")] = template.id;}console.log(JSON.stringify({ templateIds }));
templates/new-device-alert.json
{"notification": {"name": "New device sign-in alert","tags": ["recipe", "new-device-login-alert"],"brand": null,"subscription": null,"routing": null,"content": {"version": "2022-01-01","elements": [{"type": "channel","channel": "email","elements": [{"type": "meta","title": "New sign-in to your Acme account"},{"type": "text","content": "Your Acme account was used to sign in from a new device."},{"type": "text","content": "Device: {{device}}"},{"type": "text","content": "Location: {{location}}"},{"type": "text","content": "Time: {{datetime-format signed_in_at \"MMM d, yyyy, h:mm a zzz\" (default profile.timezone \"America/New_York\")}}"},{"type": "action","content": "Wasn't you? Secure your account","href": "https://app.acme.com/security"},{"type": "text","content": "If this was you, there's nothing to do."}]},{"type": "channel","channel": "inbox","elements": [{"type": "meta","title": "New sign-in from {{device}}"},{"type": "text","content": "{{location}}, {{datetime-format signed_in_at \"MMM d, yyyy, h:mm a zzz\" (default profile.timezone \"America/New_York\")}}. If this wasn't you, secure your account."},{"type": "action","content": "Wasn't you? Secure your account","href": "https://app.acme.com/security"}]}]}},"state": "PUBLISHED"}
Agents
Hand it to your agent
Give this prompt to Claude Code, Cursor, or Codex with the Courier skill installed. Agents can also read the markdown version and the manifest.
Data
Data fields
| Field | Type | Example | Note |
|---|---|---|---|
| device | string | Chrome on macOS | |
| location | string | Austin, Texas, US | An approximate place is enough |
| signed_in_at | string | 2026-10-15T14:42:00Z | An ISO timestamp. The template formats it as a date and time in the recipient's timezone |
FAQ
Frequently asked questions
How do I send a new device login alert?
Detect the new device in your app, then send this template with the device, location, and time. Routing method all sends the email and the Inbox copy at the same time.
What should a new device login alert include?
The device, an approximate location, the time, and a clear way to act if it wasn't them. Keep it short, so people can decide in a few seconds.
What happens if the user has no email address?
With routing method all, Courier sends on every channel it can. With no email address on the profile, the Inbox copy still goes out and the email is skipped.
How do I decide what counts as a new device?
That's your app's call: Courier delivers the alert but doesn't detect devices. A common approach is to remember a device after a successful sign-in and send the alert when a sign-in comes from one you haven't seen.
Recipes
Related recipes
Send a password reset email with a link that expires, and never deliver a stale one.
Send a login code by SMS, fall back to email, and stop retrying once the code is stale.
Sign people in with an emailed link, and never deliver one that has already expired.
Build this in your workspace
Run setup.ts against a free account and the template is ready to send. Prefer no code? Rebuild it in Design Studio.