This is the official node.js module for sending notifications with node.js with the Courier REST API.
Courier docs • 3 Different Ways To Send Emails With Node.js
Installation (via npm)
npm install @trycourier/courier
You will need to get a Courier API key to get started. You can sign up and create one for free at courier.com.
v6 of our SDK is automatically generated by Fern. v6 comes with several improvements that we describe below:
- Resource-scoped SDK methods: Endpoints are scoped under their resource. For
example, instead of
courier.deleteBrands
the SDK now readscourier.brands.delete(...)
- Docs on Hover: All endpoint and parameter level documentation that you see on our docs website is now embedded directly within the SDKs.
- Retries with exponential backoff: The SDK will automatically retry failures with
exponential backoff. You can configure the retries by setting
maxRetries
.const response = courier.send(..., { maxReries: 0 // set to 0 if you want to disable retries })
- Configurable Timeouts: The SDK defaults to a 60 second timeout. You can also
configure this value per-request.
const response = courier.send(..., { timeoutInSeconds: 45 // set to 45 seconds for this particular request })
- Support for multiple runtimes: The SDK uses global fetch when available, otherwise defaults to node-fetch. This means you can use Courier in multiple runtimes; Node.js, Vercel, and Cloudflare Workers.
v4 uses native fetch client to make requests or falls back to a polyfill if the client doesn't exist in the environment it's running in. Check Error Handling out.
import { CourierClient } from "@trycourier/courier";
const courier = new CourierClient({ authorizationToken: "<AUTH_TOKEN>" }); // get from the Courier UI
// Example: send a basic message to an email recipient
const { requestId } = await courier.send({
message: {
to: {
data: {
name: "Marty",
},
email: "[email protected]",
},
content: {
title: "Back to the Future",
body: "Oh my {{name}}, we need 1.21 Gigawatts!",
},
routing: {
method: "single",
channels: ["email"],
},
},
});
// Example: send a basic message to an sms recipient
const { requestId } = await courier.send({
message: {
to: {
data: {
name: "Jenny",
},
phone_number: "8675309",
},
content: {
title: "Back to the Future",
body: "Oh my {{name}}, we need 1.21 Gigawatts!",
},
routing: {
method: "single",
channels: ["sms"],
},
},
});
// Example: send a message to various recipients
const { requestId } = await courier.send({
message: {
to: [
{
user_id: "<USER_ID>", // usually your system's User ID associated to a Courier profile
email: "[email protected]",
data: {
name: "some user's name",
},
},
{
email: "[email protected]",
data: {
name: "Marty",
},
},
{
email: "[email protected]",
data: {
name: "Doc",
},
},
{
phone_number: "8675309",
data: {
name: "Jenny",
},
},
],
content: {
title: "Back to the Future",
body: "Oh my {{name}}, we need 1.21 Gigawatts!",
},
routing: {
method: "all",
channels: ["sms", "email"],
},
},
});
// Example: send a message supporting email & SMS
const { requestId } = await courier.send({
message: {
template: "<TEMPLATE_OR_EVENT_ID>", // get from the Courier UI
to: {
user_Id: "<USER_ID>", // usually your system's User ID
email: "[email protected]",
phone_number: "555-228-3890",
},
data: {}, // optional variables for merging into templates
},
});
// Example: send a message to a list
const { requestId } = await courier.send({
message: {
template: "<TEMPLATE_OR_EVENT_ID>", // get from the Courier UI
to: {
list_id: "<LIST_ID>", // e.g. your Courier List Id
},
data: {}, // optional variables for merging into templates
},
});
// Example: send a message to a pattern
const { requestId } = await courier.send({
message: {
template: "<TEMPLATE_OR_EVENT_ID>", // get from the Courier UI
to: {
list_pattern: "<PATTERN>", // e.g. example.list.*
},
data: {}, // optional variables for merging into templates
},
});
// Example: send a message to a list, pattern and user
const { requestId } = await courier.send({
message: {
content: {
title: "Hello",
body: "Good morning!"
},
to: [
{
list_pattern: "<PATTERN>", // e.g. example.list.*
},
{
list_id: "<LIST_ID>", // e.g. your Courier List Id
},
{
email: "[email protected]"
}
]
},
routing: {
method: "single",
channels: ["email"],
},
});
// Example: send a basic message that expires after the specified timeout
const { requestId } = await courier.send({
message: {
to: {
data: {
name: "Marty",
},
email: "[email protected]",
},
content: {
title: "Back to the Future",
body: "Oh my {{name}}, we need 1.21 Gigawatts!",
},
routing: {
method: "single",
channels: ["email"],
},
timeout: {
message: 3600000 // 1 hour in milliseconds
},
},
});
// Example: send a basic message with a trace id
const { requestId } = await courier.send({
message: {
to: {
data: {
name: "Marty",
},
email: "[email protected]",
},
content: {
title: "Back to the Future",
body: "Oh my {{name}}, we need 1.21 Gigawatts!",
},
routing: {
method: "single",
channels: ["email"],
},
metadata: {
trace_id: "ravenclaw-for-the-win"
},
},
});
courier-node
supports credential storage in environment variables. If no authorizationToken
is provided when instantiating the Courier client (e.g., const courier = CourierClient();
), the value in the COURIER_AUTH_TOKEN
env var will be used.
If you need to use a base url other than the default https://api.courier.com, you can set it using the COURIER_BASE_URL
env var.
const { CourierClient } = require("@trycourier/courier");
const courier = CourierClient({ authorizationToken: "<AUTH_TOKEN>" });
async function run() {
// Example: send a message
const { requestId } = await courier.send({
message: {
template: "<TEMPLATE_OR_EVENT_ID>",
to: {
// optional
user_id: "<RECIPIENT_ID>",
},
data: {}, // optional
brand_id: "<BRAND_ID>", //optional
channels: {}, // optional
providers: {}, // optional
},
});
console.log(requestId);
// Example: send message with utm metadata
const { requestId } = await courier.send({
message: {
template: "<TEMPLATE_OR_EVENT_ID>",
to: {...},
routing: {
method: "single",
channels: ["email"],
},
channels: {
email: {
routing_method: "all",
providers: ["sendgrid", "sns"],
metadata: {
utm: {
medium: "f",
campaign: "g",
},
},
},
},
providers: {
sns: {
metadata: {
utm: {
medium: "h",
},
},
},
}, // optional
metadata: {
utm: {
source: "a",
medium: "b",
campaign: "c",
},
},
timeout: {
message: 300000,
channel: {
email: 1000 // 1 second
}
}
},
});
/**
* If the template or content contains any action blocks, the hyperlinks will be augmented with utm compliant query parameters.
*
* The resulting link of an action block sent through sendgrid would be:
* www.example.com?utm_source=a&utm_medium=f&utm_campaign=g
*
* While the resulting link of an action block sent through sns would be:
* www.example.com?utm_source=a&utm_medium=h&utm_campaign=g
*
* Notice that provider metadata supersedes channel metadata and channel metadata supersedes message metadata
*
**/
/**
* If the message includes a timeout property we will start timing out messages after the first attempt.
* We are able to timeout complete channels or specific providers.
**/
// Example: get a message status
const messageStatus = await courier.messages.get(requestId);
console.log(messageStatus);
// Example: get a message history
const { results } = await courier.messages.getHistory(requestId);
console.log(results);
// Example: get a message output
const { results } = await courier.messages.getContent(requestId);
console.log(results);
// Example: get all messages
const { paging, results } = await courier.messages.list();
console.log(results);
// Example: replace a recipient's profile
const { status: replaceStatus } = await courier.profiles.replace(
"<RECIPIENT_ID>",
{
profile: {
email: "[email protected]",
},
}
);
console.log(replaceStatus);
// Example: merge into a recipient's profile
const { status: mergeStatus } = await courier.profiles.create(
"<RECIPIENT_ID>",
{
profile: {
sms: "555-555-5555",
},
}
);
console.log(mergeStatus);
// Example: get a recipient's profile
const { profile } = await courier.profiles.get("<RECIPIENT_ID>");
console.log(profile);
// Example: get all brands
const { paging, results } = await courier.brands.list({
cursor: "<CURSOR>", // optional
});
console.log(results);
// Example: get a specific brand
const brand = await courier.brands.get("<BRAND_ID>");
console.log(brand);
// Example: create a brand
const newBrand = await courier.brands.create({
name: "My Brand",
settings: {
colors: {
primary: "#0000FF",
secondary: "#FF0000",
tertiary: "#00FF00",
},
},
});
console.log(newBrand);
// Example: replace a brand
const replacedBrand = await courier.brands.replace("<BRAND_ID>", {
name: "My New Brand",
settings: {
colors: {
primary: "#FF0000",
secondary: "#00FF00",
tertiary: "#0000FF",
},
},
});
console.log(replacedBrand);
// Example: delete a brand
await courier.brands.delete("<BRAND_ID>");
// Example: get all lists
const { paging, items } = await courier.lists.list({
cursor: "<CURSOR>", // optional
});
console.log(items);
// Example: get a specific list
const list = await courier.lists.get("<LIST_ID>");
console.log(list);
// Example: create or replace a list
const replacedList = await courier.lists.update("<LIST_ID>", {
name: "My New List",
});
console.log(replacedList);
// Example: delete a list
await courier.lists.delete("<LIST_ID>");
// Example: restore a list
await courier.lists.restore("<LIST_ID>");
// Example: get a list's subscribers
const { paging, items } = await courier.lists.getSubscribers("<LIST_ID>");
console.log(items);
// Example: replace many recipients to a new or existing list
await courier.lists.updateSubscribers("<LIST_ID>", [
{ recipientId: "RECIPIENT_ID_1" },
{ recipientId: "RECIPIENT_ID_2" },
]);
// Example: subscribe single recipient to a new or existing list
await courier.lists.subscribe("<LIST_ID>", "<RECIPIENT_ID>");
console.log(recipient);
// Example: unsubscribe recipient from list
await courier.lists.unsubscribe("<LIST_ID>", "<RECIPIENT_ID>");
// Example: get a recipient's subscribed lists
const { paging, items } = await courier.lists.getSubscribers(
"<RECIPIENT_ID>"
);
console.log(items);
// Example: Automation Ad-Hoc Invoke
const { runId } = await courier.automations.invokeAdHocAutomation({
automation: {
cancelation_token: "I_AM_TOKEN",
steps: [
{
action: "send",
},
],
},
brand: "BRAND_ID",
data: {
example: "EXAMPLE_DATA",
},
profile: {
email: "[email protected]",
},
recipient: "RECIPIENT_ID",
template: "TEMPLATE_NAME_OR_ID",
});
console.log(runId);
// Example: Automation Invoke Template
const { runId } = await courier.automations.invokeAutomationTemplate(
"AUTOMATION_TEMPLATE_ID",
{
brand: "BRAND_ID",
data: {
example: "EXAMPLE_DATA",
},
profile: {
email: "[email protected]",
},
recipient: "RECIPIENT_ID",
template: "TEMPLATE_NAME_OR_ID",
}
);
console.log(runId);
// Example: List notifications
const { paging, results } = await courier.notifications.list();
console.log(results);
// Example: Get notification content
const { blocks, channels } = await courier.notifications.getContent(
"notification1"
);
console.log(blocks);
console.log(channels);
// Example: Get notification draft content
const { blocks, channels } = await courier.notifications.getDraftContent(
"notification1"
);
console.log(blocks);
console.log(channels);
// Example: Post notification variations
await courier.notifications.updateVariations("notification1", {
blocks: [
{
id: "block_1d4c32e0-bca8-43f6-b5d5-8c043199bce6",
type: "text",
locales: {
fr_FR: "block fr 1",
},
},
{
id: "block_6d50a6e3-ecc3-4815-bf51-0202c6bf54e2",
type: "text",
locales: {
fr_FR: "block fr 2",
},
},
],
channels: [
{
id: "channel_1ba46024-f156-4ed7-893b-cb1cdcfbd36e",
type: "email",
locales: {
fr_FR: {
subject: "French Subject",
},
},
},
{
id: "channel_2c2aad1c-30f0-4a55-8d8f-d213f32147bc",
type: "push",
locales: {
fr_FR: {
title: "French Title",
},
},
},
],
});
// Example: Post notification draft variations
await courier.notifications.updateDraftVariations("notification1", {
blocks: [
{
id: "block_1d4c32e0-bca8-43f6-b5d5-8c043199bce6",
type: "text",
locales: {
fr_FR: "block fr 1",
},
},
{
id: "block_6d50a6e3-ecc3-4815-bf51-0202c6bf54e2",
type: "text",
locales: {
fr_FR: "block fr 2",
},
},
],
channels: [
{
id: "channel_1ba46024-f156-4ed7-893b-cb1cdcfbd36e",
type: "email",
locales: {
fr_FR: {
subject: "French Subject",
},
},
},
{
id: "channel_2c2aad1c-30f0-4a55-8d8f-d213f32147bc",
type: "push",
locales: {
fr_FR: {
title: "French Title",
},
},
},
],
});
// Example: Get notification submission checks
const { checks } = await courier.notifications.getSubmissionChecks(
"notification1",
"submission1"
);
console.log(checks);
// Example: Put notification submission checks
const { checks } = await courier.notifications.replaceSubmissionChecks(
"notification1",
"submission1",
{
checks: [
{
id: "check1",
status: "RESOLVED",
type: "custom",
},
],
}
);
console.log(checks);
// Example: Cancel notification submission
await courier.notifications.cancelSubmission("notification1", "submission1");
// Bulk Processing
// Example: create a job (API v1 semantics)
const response = await courier.bulk.createJob({
message: {
event: "RR4NDQ7NZ24A8TKPWVBEDGE15E9A",
},
});
console.log(response);
// Example: create a job (API v2 semantics)
const response = await courier.bulk.createJob({
message: {
message: {
template: "RR4NDQ7NZ24A8TKPWVBEDGE15E9A",
},
},
});
console.log(response);
// Example: get a job
const response = await courier.bulk.getJob({
jobId: "1-61efe386-6ff57552409e311b7a1f371f",
});
console.log(response);
// Example: Ingest users in a job (API v1 semantics)
const response = await courier.bulk.ingestUsers({
jobId: "1-61efe386-6ff57552409e311b7a1f371f",
users: [
{
profile: {
email: "[email protected]",
},
},
],
});
console.log(response);
// Example: Ingest users in a job (API v2 semantics)
const response = await courier.bulk.ingestUsers({
jobId: "1-61efe386-6ff57552409e311b7a1f371f",
users: [
{
to: {
email: "[email protected]",
},
},
],
});
console.log(response);
// Example: Run a job
await courier.bulk.runJob("1-61efe386-6ff57552409e311b7a1f371f");
// Example: Get user details in a job
const response = await courier.bulk.getUsers(
"1-61efe386-6ff57552409e311b7a1f371f"
);
console.log(response);
}
run();
This package tries to use the native fetch
client to make requests or falls back to a polyfill if the client doesn't exist in the environment it's running in.
All network related promise rejections are not handled in any way. All successfully made requests that produce errors on the server side are resulting in promise rejections with custom CourierError
error type.
CourierError
extends native Error
interface with two extra properties:
statusCode
: this is the status code of the responsebody
: this is the body of the response
// Error handling example
import { CourierError, CourierClient } from "@trycourier/courier";
const courier = new CourierClient();
try {
await courier.send(/* ... */);
} catch (error) {
if (error instanceof CourierError) {
console.log("Failed to send with status code:", error.statusCode);
console.log("The Courier body is:", error.body);
console.log("The error message is:", error.message);
} else {
console.log(
"There was a problem making that request. Make sure you are online."
);
}
}
For POST
methods, you can supply an idempotencyKey
in the config parameter to ensure the idempotency of the API Call. We recommend that you use a V4 UUID
for the key. Keys are eligible to be removed from the system after they're at least 24 hours old, and a new request is generated if a key is reused after the original has been removed. For more info, see Idempotent Requests in the Courier documentation.
import { CourierClient } from "@trycourier/courier";
import uuid4 from "uuid4";
const courier = new CourierClient();
const idempotencyKey = uuid4();
async function run() {
const { requestId } = await courier.send(
{
template: "<TEMPLATE_OR_EVENT_ID>",
to: {
user_id: "<USER_ID>",
email: "[email protected]",
phone_number: "555-867-5309",
},
data: {
world: "JavaScript!",
},
},
{
idempotencyKey,
}
);
console.log(requestId);
}
run();
Audiences APIs are used to create, get, update, and delete audiences. A Courier Audience is a dynamic group of users (created using Courier's Profile API) that matches a set of criteria. Audience is reactive to changes in the user's profile. As you change user profile using profiles
API, the audience will be updated accordingly. You will not have to maintain a list of users in your audience. Courier takes care of that for you. If you have potentially large set of users, you first create an audience and then use the audience's id to retrieve the list of users. Once you satified with the calculated list of users, you can use the audienceId
to send notification using send
API.
// Example: create audience which would allow sending notification to all users that match the given filter criteria
const { audienceId } = await courier.audiences.put({
id: "<AUDIENCE_ID>",
filter: {
operator: "EQ",
path: "title",
value: "Software Engineer",
},
});
// To retrieve list of members in a given audience, you can use the following:
const { items: audienceMembers } = await courier.audiences.listMembers(
audienceId
cursor: "<CURSOR>", // optional
);
// To send a notification to all users that match the given filter criteria, you can use the following:
const { requestId } = await courier.send({
message: {
template: "<TEMPLATE_OR_EVENT_ID>", // This can be inline content as well
to: {
audience_id: audienceId,
},
data: {}, // optional
brand_id: "<BRAND_ID>", //optional
routing: {},
channels: {}, // optional
providers: {}, // optional
},
});
The Tenants API is designed to enable multi-tenant notification workflows. This is useful for defining user to tenant level relationships, especially in the context of B2B applications.
Use Cases:
- Sending branded notifications on behalf of an organization
- Creating slack-bots on behalf of an organization
const { id } = await courier.tenants.createOrReplace("<TENANT_ID>", {
name: "Courier",
user_profile: {
slack: {
access_token: "<SLACK_ACCESS_TOKEN_SCOPED_TO_THE_TENANT>",
},
},
});
const tenant = await courier.tenants.get("<TENANT_ID>");
await courier.tenants.delete("<TENANT_ID>");
const { items: tenants, has_more } = await courier.tenants.list();
Courier currently does not allow creating new topics via the API. You must create topics via the Courier UI. Once a topic is created, you can update a user's preferences for that topic via the API.
await courier.users.preferences.update("<USER_ID>", "<VALID_TOPIC_ID>", {
default_status: "OPTED_IN",
status: "OPTED_OUT",
});
- Get all topic level preferences for a user
const { items: userPreferences } = await courier.users.preferences.list(
"<USER_ID>"
);
- Get a specific topic level preference for a user
const userPreference = await courier.users.preferences.get(
"<USER_ID>",
"<VALID_TOPIC_ID>"
);
While we value open-source contributions to this SDK, this library is generated programmatically. Additions made directly to this library would have to be moved over to our generation code, otherwise they would be overwritten upon the next generated release. Feel free to open a PR as a proof of concept, but know that we will not be able to merge it as-is. We suggest opening an issue first to discuss with us!
On the other hand, contributions to the README are always very welcome!