Prerequisites
Setup
Open the in Courier, enter your bot token, then save.Scopes
Update thebot scope with these permissions:
View ChannelsSend Messages- Optional:
Read Message Historyto send a message as a reply to another message
Profile requirements
Discord addresses the recipient by user ID or channel ID, underdiscord on the profile. Store either one once with , which merges into the profile and creates it if it does not exist:
- Direct message
- Channel
1
Open User Settings
Go to User Settings on Discord (next to profile on the bottom left).
2
Enable Developer Mode
Open Advanced settings and enable Developer Mode .
3
Copy the user ID
Right click on the user and copy the user ID.
const profile = await client.profiles.create('user_123', {
profile: {
discord: {
user_id: '617099137532932107',
},
},
});
profile = client.profiles.create(
user_id="user_123",
profile={
"discord": {
"user_id": "617099137532932107",
},
},
)
curl --request POST \
--url https://api.courier.com/profiles/user_123 \
--header "Authorization: Bearer $COURIER_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"profile": {
"discord": {
"user_id": "617099137532932107"
}
}
}'
profile = courier.profiles.create(
"user_123",
profile: {
discord: {
user_id: "617099137532932107"
}
}
)
profile, err := client.Profiles.New(
context.TODO(),
"user_123",
courier.ProfileNewParams{
Profile: map[string]any{
"discord": map[string]any{
"user_id": "617099137532932107",
},
},
},
)
ProfileCreateParams params = ProfileCreateParams.builder()
.userId("user_123")
.profile(ProfileCreateParams.Profile.builder()
.putAdditionalProperty("discord", JsonValue.from(java.util.Map.of(
"user_id", "617099137532932107")))
.build())
.build();
ProfileCreateResponse profile = client.profiles().create(params);
$profile = $client->profiles->create('user_123', profile: [
'discord' => [
'user_id' => '617099137532932107',
],
]);
ProfileCreateParams parameters = new()
{
UserID = "user_123",
Profile = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"discord": {
"user_id": "617099137532932107"
}
}
"""
),
};
var profile = await client.Profiles.Create(parameters);
courier profiles create \
--api-key "$COURIER_API_KEY" \
--user-id user_123 \
--profile '{"discord":{"user_id":"617099137532932107"}}'
With Courier MCP, save the Discord user id 617099137532932107 on user_123.
1
Open User Settings
Go to User Settings on Discord (next to profile on the bottom left).
2
Enable Developer Mode
Open Advanced settings and enable Developer Mode .
3
Copy the channel ID
Right click on the channel and copy the channel ID.
const profile = await client.profiles.create('user_123', {
profile: {
discord: {
channel_id: '768866348853383208',
},
},
});
profile = client.profiles.create(
user_id="user_123",
profile={
"discord": {
"channel_id": "768866348853383208",
},
},
)
curl --request POST \
--url https://api.courier.com/profiles/user_123 \
--header "Authorization: Bearer $COURIER_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"profile": {
"discord": {
"channel_id": "768866348853383208"
}
}
}'
profile = courier.profiles.create(
"user_123",
profile: {
discord: {
channel_id: "768866348853383208"
}
}
)
profile, err := client.Profiles.New(
context.TODO(),
"user_123",
courier.ProfileNewParams{
Profile: map[string]any{
"discord": map[string]any{
"channel_id": "768866348853383208",
},
},
},
)
ProfileCreateParams params = ProfileCreateParams.builder()
.userId("user_123")
.profile(ProfileCreateParams.Profile.builder()
.putAdditionalProperty("discord", JsonValue.from(java.util.Map.of(
"channel_id", "768866348853383208")))
.build())
.build();
ProfileCreateResponse profile = client.profiles().create(params);
$profile = $client->profiles->create('user_123', profile: [
'discord' => [
'channel_id' => '768866348853383208',
],
]);
ProfileCreateParams parameters = new()
{
UserID = "user_123",
Profile = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"discord": {
"channel_id": "768866348853383208"
}
}
"""
),
};
var profile = await client.Profiles.Create(parameters);
courier profiles create \
--api-key "$COURIER_API_KEY" \
--user-id user_123 \
--profile '{"discord":{"channel_id":"768866348853383208"}}'
With Courier MCP, save the Discord channel id 768866348853383208 on user_123.
user_id and Courier resolves the address.
For a one-off with no stored profile, pass it inline instead: "to": { "discord": { "user_id": "617099137532932107" } }.
Send by user id
The call in every language, and the rest of the profile object.
Send to a recipient
- Send to user id
- Send to `discord`
Courier reads
discord off the saved profile, so preferences apply and the value can change without touching this code.const { requestId } = await courier.send.message({
message: {
to: {
user_id: "user_123",
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
},
});
response = client.send.message(
message={
"to": {
"user_id": "user_123",
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": {
"user_id": "user_123"
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40"
}
}'
response = courier.send_.message(
message: {
to: {
user_id: "user_123"
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40"
}
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfUserRecipient: &shared.UserRecipientParam{
UserID: courier.String("user_123"),
},
},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(UserRecipient.builder().userId("user_123").build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'to' => [
'user_id' => 'user_123',
],
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
],
);
SendMessageParams parameters = new()
{
Message = new()
{
To = new UserRecipient { UserID = "user_123" },
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"user_id": "user_123"}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40
With Courier MCP, send my template to user_123 by email.
Pass it inline instead and nothing is stored. Swap this
to object into the call on the other tab.const { requestId } = await courier.send.message({
message: {
// A provider recipient is a profile field, so it sits outside the typed union.
to: {
discord: {
user_id: "617099137532932107",
},
} as any,
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
},
});
response = client.send.message(
message={
"to": {
"discord": {
"user_id": "617099137532932107",
},
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": {
"discord": {
"user_id": "617099137532932107"
}
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40"
}
}'
response = courier.send_.message(
message: {
to: {
discord: {
user_id: "617099137532932107"
}
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40"
}
)
// A provider recipient is a profile field, so it sits outside the typed union.
to := param.Override[shared.UserRecipientParam](
json.RawMessage(`{"discord": {"user_id": "617099137532932107"}}`),
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{OfUserRecipient: &to},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
},
})
// A provider recipient is a profile field, so it sits outside the typed union.
UserRecipient to = UserRecipient.builder()
.putAdditionalProperty("discord", JsonValue.from(Map.of(
"user_id", "617099137532932107")))
.build();
client.send().message(SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(to)
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.build())
.build());
$response = $client->send->message(
message: [
'to' => [
'discord' => [
'user_id' => '617099137532932107',
],
],
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
],
);
// A provider recipient is a profile field, so it sits outside the typed union.
UserRecipient to = UserRecipient.FromRawUnchecked(
JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""{"discord": {"user_id": "617099137532932107"}}"""
)
);
var response = await client.Send.Message(new() { Message = new() { To = to, Template = "nt_01kx4h2jdafq8bk9aftxak4b40" } });
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"discord": {"user_id": "617099137532932107"}}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40
With Courier MCP, send my template to the Discord user 617099137532932107.
Overrides
covers the two levels and which one wins. A provider override changes what Courier sends to Discord’s Create Message endpoint. Overrides are deep-merged into the request. Fields you set replace their counterparts, and everything you leave out is still sent. For example, add an embed.const { requestId } = await courier.send.message({
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
discord: {
channel_id: "768866348853383208",
},
},
providers: {
discord: {
override: {
body: {
embed: {
title: "Hello, Embed!",
description: "This is an embedded message.",
},
},
},
},
},
},
});
response = client.send.message(
message={
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"discord": {
"channel_id": "768866348853383208",
},
},
"providers": {
"discord": {
"override": {
"body": {
"embed": {
"title": "Hello, Embed!",
"description": "This is an embedded message.",
},
},
},
},
},
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"discord": {
"channel_id": "768866348853383208"
}
},
"providers": {
"discord": {
"override": {
"body": {
"embed": {
"title": "Hello, Embed!",
"description": "This is an embedded message."
}
}
}
}
}
}
}'
response = courier.send_.message(
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
discord: {
channel_id: "768866348853383208"
}
},
providers: {
discord: {
override: {
body: {
embed: {
title: "Hello, Embed!",
description: "This is an embedded message."
}
}
}
}
}
}
)
// This provider addresses the recipient with fields outside the typed
// UserRecipient model, so pass the recipient as raw JSON.
to := param.Override[shared.UserRecipientParam](json.RawMessage(`{
"discord": {
"channel_id": "768866348853383208"
}
}`))
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{OfUserRecipient: &to},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
Providers: shared.MessageProvidersParam{
"discord": shared.MessageProvidersTypeParam{
Override: map[string]any{
"body": map[string]any{
"embed": map[string]any{
"title": "Hello, Embed!",
"description": "This is an embedded message.",
},
},
},
},
},
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
// This provider addresses the recipient with fields outside the
// typed UserRecipient model, so pass them as additional properties.
.to(UserRecipient.builder()
.putAdditionalProperty("discord", JsonValue.from(java.util.Map.of(
"channel_id", "768866348853383208"
)))
.build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.providers(MessageProviders.builder()
.putAdditionalProperty("discord", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
"body", java.util.Map.of(
"embed", java.util.Map.of(
"title", "Hello, Embed!",
"description", "This is an embedded message."
)
)
))))
.build())
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
'to' => [
'discord' => [
'channel_id' => '768866348853383208',
],
],
'providers' => [
'discord' => [
'override' => [
'body' => [
'embed' => [
'title' => 'Hello, Embed!',
'description' => 'This is an embedded message.',
],
],
],
],
],
],
);
SendMessageParams parameters = new()
{
Message = new()
{
// This provider addresses the recipient with fields outside the
// typed UserRecipient model, so build it from raw JSON.
To = UserRecipient.FromRawUnchecked(
JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"discord": {
"channel_id": "768866348853383208"
}
}
"""
)
),
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
Providers = new Dictionary<string, MessageProvidersType>()
{
{
"discord",
new()
{
Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"body": {
"embed": {
"title": "Hello, Embed!",
"description": "This is an embedded message."
}
}
}
"""
),
}
},
},
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"discord": {"channel_id": "768866348853383208"}}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
--message.providers '{"discord": {"override": {"body": {"embed": {"title": "Hello, Embed!", "description": "This is an embedded message."}}}}}'
With Courier MCP, send my template to Discord channel 768866348853383208 with a body override.
Provider details
discord
routing.channels instead is supported, and sends through just this provider.
Send to a specific provider
When that is worth doing, and what you give up: failover, channel priority, and providers you add later.