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.

Email

From: Acme
New sign-in to your Acme account

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.

Wasn't you? Secure your account

In-app Inbox

New sign-in from Chrome on macOS

Austin, Texas, US, Oct 15, 2026, 10:42 AM EDT. If this wasn't you, secure your account.

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 datetime
from courier import Courier
client = 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.

Add new device login alerts to this app, sent through Courier: an email and an Inbox message at the same time, with the device, location, and time and a Wasn't you? Secure your account button. Follow the steps at https://www.courier.com/recipes/new-device-login-alert.md and use the Courier skill for SDK details. 1. Create the template in my Courier workspace by running setup.ts from that page with COURIER_API_KEY from our environment. Save the template ID it prints as COURIER_NEW_DEVICE_TEMPLATE_ID. 2. Find where this codebase signs a user in and decides whether the device is new, and send the alert there with the recipe's send code. Pass device, location, and signed_in_at (an ISO timestamp) from our own data. Replace the placeholder product name (Acme) and the Secure your account link (https://app.acme.com/security) in the template with ours. 3. Match this codebase's language, file layout, and error handling. Never hardcode the API key. 4. When you're done, send one alert to a test user and show me both deliveries, email and Inbox.

Data

Data fields

FieldTypeExampleNote
devicestringChrome on macOS
locationstringAustin, Texas, USAn approximate place is enough
signed_in_atstring2026-10-15T14:42:00ZAn 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.

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.