Prerequisites
- An SMTP server you can send through
- The host, username, and password for that server
Setup
Courier’s SMTP integration uses NodeMailer. Open the in Courier, enter your SMTP host, username, password, and From Address, then save. A provider override can change any of these for a single message.Courier connects to your SMTP server from AWS-hosted infrastructure and does not use fixed outbound IPs. If your server requires IP allowlisting, see on the Email Providers page.
Overrides
covers the two levels and which one wins. lists the fields every email provider takes. Provider overrides in the Send API change the message content and the SMTP transport configuration, one message at a time.Message override
An override changes what Courier sends over SMTP through NodeMailer. 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 attachment:const { requestId } = await courier.send.message({
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
email: "sarah@acme-corp.com",
},
providers: {
smtp: {
override: {
body: {
attachments: [
{
filename: "document.pdf",
content: "aGVsbG8gd29ybGQh",
encoding: "base64",
contentType: "application/pdf",
},
],
},
},
},
},
},
});
response = client.send.message(
message={
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"email": "sarah@acme-corp.com",
},
"providers": {
"smtp": {
"override": {
"body": {
"attachments": [
{
"filename": "document.pdf",
"content": "aGVsbG8gd29ybGQh",
"encoding": "base64",
"contentType": "application/pdf",
},
],
},
},
},
},
},
)
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": {
"email": "sarah@acme-corp.com"
},
"providers": {
"smtp": {
"override": {
"body": {
"attachments": [
{
"filename": "document.pdf",
"content": "aGVsbG8gd29ybGQh",
"encoding": "base64",
"contentType": "application/pdf"
}
]
}
}
}
}
}
}'
response = courier.send_.message(
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
email: "sarah@acme-corp.com"
},
providers: {
smtp: {
override: {
body: {
attachments: [
{
filename: "document.pdf",
content: "aGVsbG8gd29ybGQh",
encoding: "base64",
contentType: "application/pdf"
}
]
}
}
}
}
}
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfUserRecipient: &shared.UserRecipientParam{
Email: courier.String("sarah@acme-corp.com"),
},
},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
Providers: shared.MessageProvidersParam{
"smtp": shared.MessageProvidersTypeParam{
Override: map[string]any{
"body": map[string]any{
"attachments": []any{
map[string]any{
"filename": "document.pdf",
"content": "aGVsbG8gd29ybGQh",
"encoding": "base64",
"contentType": "application/pdf",
},
},
},
},
},
},
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(UserRecipient.builder().email("sarah@acme-corp.com").build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.providers(MessageProviders.builder()
.putAdditionalProperty("smtp", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
"body", java.util.Map.of(
"attachments", java.util.List.of(
java.util.Map.of(
"filename", "document.pdf",
"content", "aGVsbG8gd29ybGQh",
"encoding", "base64",
"contentType", "application/pdf"
)
)
)
))))
.build())
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
'to' => [
'email' => 'sarah@acme-corp.com',
],
'providers' => [
'smtp' => [
'override' => [
'body' => [
'attachments' => [
[
'filename' => 'document.pdf',
'content' => 'aGVsbG8gd29ybGQh',
'encoding' => 'base64',
'contentType' => 'application/pdf',
],
],
],
],
],
],
],
);
SendMessageParams parameters = new()
{
Message = new()
{
To = new UserRecipient { Email = "sarah@acme-corp.com" },
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
Providers = new Dictionary<string, MessageProvidersType>()
{
{
"smtp",
new()
{
Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"body": {
"attachments": [
{
"filename": "document.pdf",
"content": "aGVsbG8gd29ybGQh",
"encoding": "base64",
"contentType": "application/pdf"
}
]
}
}
"""
),
}
},
},
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"email": "sarah@acme-corp.com"}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
--message.providers '{"smtp": {"override": {"body": {"attachments": [{"filename": "document.pdf", "content": "aGVsbG8gd29ybGQh", "encoding": "base64", "contentType": "application/pdf"}]}}}}'
With Courier MCP, send my template to sarah@acme-corp.com and override what goes out over SMTP.
message.providers.smtp.override.body into its generated message and passes it to NodeMailer. For every option, see the NodeMailer message options documentation.
Transport override
Values inmessage.providers.smtp.override.config override the SMTP transport configuration. They replace your stored provider configuration for that one message.
Basic example:
const { requestId } = await courier.send.message({
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
email: "sarah@acme-corp.com",
},
providers: {
smtp: {
override: {
config: {
auth: {
user: "username",
pass: "hunter2",
},
host: "smtp.example.com",
secure: true,
port: 465,
},
},
},
},
},
});
response = client.send.message(
message={
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"email": "sarah@acme-corp.com",
},
"providers": {
"smtp": {
"override": {
"config": {
"auth": {
"user": "username",
"pass": "hunter2",
},
"host": "smtp.example.com",
"secure": True,
"port": 465,
},
},
},
},
},
)
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": {
"email": "sarah@acme-corp.com"
},
"providers": {
"smtp": {
"override": {
"config": {
"auth": {
"user": "username",
"pass": "hunter2"
},
"host": "smtp.example.com",
"secure": true,
"port": 465
}
}
}
}
}
}'
response = courier.send_.message(
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
email: "sarah@acme-corp.com"
},
providers: {
smtp: {
override: {
config: {
auth: {
user: "username",
pass: "hunter2"
},
host: "smtp.example.com",
secure: true,
port: 465
}
}
}
}
}
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfUserRecipient: &shared.UserRecipientParam{
Email: courier.String("sarah@acme-corp.com"),
},
},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
Providers: shared.MessageProvidersParam{
"smtp": shared.MessageProvidersTypeParam{
Override: map[string]any{
"config": map[string]any{
"auth": map[string]any{
"user": "username",
"pass": "hunter2",
},
"host": "smtp.example.com",
"secure": true,
"port": 465,
},
},
},
},
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(UserRecipient.builder().email("sarah@acme-corp.com").build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.providers(MessageProviders.builder()
.putAdditionalProperty("smtp", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
"config", java.util.Map.of(
"auth", java.util.Map.of(
"user", "username",
"pass", "hunter2"
),
"host", "smtp.example.com",
"secure", true,
"port", 465
)
))))
.build())
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
'to' => [
'email' => 'sarah@acme-corp.com',
],
'providers' => [
'smtp' => [
'override' => [
'config' => [
'auth' => [
'user' => 'username',
'pass' => 'hunter2',
],
'host' => 'smtp.example.com',
'secure' => true,
'port' => 465,
],
],
],
],
],
);
SendMessageParams parameters = new()
{
Message = new()
{
To = new UserRecipient { Email = "sarah@acme-corp.com" },
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
Providers = new Dictionary<string, MessageProvidersType>()
{
{
"smtp",
new()
{
Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"config": {
"auth": {
"user": "username",
"pass": "hunter2"
},
"host": "smtp.example.com",
"secure": true,
"port": 465
}
}
"""
),
}
},
},
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"email": "sarah@acme-corp.com"}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
--message.providers '{"smtp": {"override": {"config": {"auth": {"user": "username", "pass": "hunter2"}, "host": "smtp.example.com", "secure": true, "port": 465}}}}'
With Courier MCP, send my template to sarah@acme-corp.com through a different SMTP server.
const { requestId } = await courier.send.message({
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
email: "sarah@acme-corp.com",
},
providers: {
smtp: {
override: {
config: {
host: "smtp.yourdomain.com",
port: 587,
secure: false,
requireTLS: true,
auth: {
user: "user@yourdomain.com",
pass: "your-password",
},
},
},
},
},
},
});
response = client.send.message(
message={
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"email": "sarah@acme-corp.com",
},
"providers": {
"smtp": {
"override": {
"config": {
"host": "smtp.yourdomain.com",
"port": 587,
"secure": False,
"requireTLS": True,
"auth": {
"user": "user@yourdomain.com",
"pass": "your-password",
},
},
},
},
},
},
)
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": {
"email": "sarah@acme-corp.com"
},
"providers": {
"smtp": {
"override": {
"config": {
"host": "smtp.yourdomain.com",
"port": 587,
"secure": false,
"requireTLS": true,
"auth": {
"user": "user@yourdomain.com",
"pass": "your-password"
}
}
}
}
}
}
}'
response = courier.send_.message(
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
email: "sarah@acme-corp.com"
},
providers: {
smtp: {
override: {
config: {
host: "smtp.yourdomain.com",
port: 587,
secure: false,
requireTLS: true,
auth: {
user: "user@yourdomain.com",
pass: "your-password"
}
}
}
}
}
}
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfUserRecipient: &shared.UserRecipientParam{
Email: courier.String("sarah@acme-corp.com"),
},
},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
Providers: shared.MessageProvidersParam{
"smtp": shared.MessageProvidersTypeParam{
Override: map[string]any{
"config": map[string]any{
"host": "smtp.yourdomain.com",
"port": 587,
"secure": false,
"requireTLS": true,
"auth": map[string]any{
"user": "user@yourdomain.com",
"pass": "your-password",
},
},
},
},
},
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(UserRecipient.builder().email("sarah@acme-corp.com").build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.providers(MessageProviders.builder()
.putAdditionalProperty("smtp", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
"config", java.util.Map.of(
"host", "smtp.yourdomain.com",
"port", 587,
"secure", false,
"requireTLS", true,
"auth", java.util.Map.of(
"user", "user@yourdomain.com",
"pass", "your-password"
)
)
))))
.build())
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
'to' => [
'email' => 'sarah@acme-corp.com',
],
'providers' => [
'smtp' => [
'override' => [
'config' => [
'host' => 'smtp.yourdomain.com',
'port' => 587,
'secure' => false,
'requireTLS' => true,
'auth' => [
'user' => 'user@yourdomain.com',
'pass' => 'your-password',
],
],
],
],
],
],
);
SendMessageParams parameters = new()
{
Message = new()
{
To = new UserRecipient { Email = "sarah@acme-corp.com" },
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
Providers = new Dictionary<string, MessageProvidersType>()
{
{
"smtp",
new()
{
Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"config": {
"host": "smtp.yourdomain.com",
"port": 587,
"secure": false,
"requireTLS": true,
"auth": {
"user": "user@yourdomain.com",
"pass": "your-password"
}
}
}
"""
),
}
},
},
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"email": "sarah@acme-corp.com"}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
--message.providers '{"smtp": {"override": {"config": {"host": "smtp.yourdomain.com", "port": 587, "secure": false, "requireTLS": true, "auth": {"user": "user@yourdomain.com", "pass": "your-password"}}}}}'
With Courier MCP, send my template to sarah@acme-corp.com over STARTTLS on port 587.
const { requestId } = await courier.send.message({
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
email: "sarah@acme-corp.com",
},
providers: {
smtp: {
override: {
config: {
host: "smtp.yourdomain.com",
port: 465,
secure: true,
auth: {
user: "user@yourdomain.com",
pass: "your-password",
},
},
},
},
},
},
});
response = client.send.message(
message={
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"email": "sarah@acme-corp.com",
},
"providers": {
"smtp": {
"override": {
"config": {
"host": "smtp.yourdomain.com",
"port": 465,
"secure": True,
"auth": {
"user": "user@yourdomain.com",
"pass": "your-password",
},
},
},
},
},
},
)
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": {
"email": "sarah@acme-corp.com"
},
"providers": {
"smtp": {
"override": {
"config": {
"host": "smtp.yourdomain.com",
"port": 465,
"secure": true,
"auth": {
"user": "user@yourdomain.com",
"pass": "your-password"
}
}
}
}
}
}
}'
response = courier.send_.message(
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
email: "sarah@acme-corp.com"
},
providers: {
smtp: {
override: {
config: {
host: "smtp.yourdomain.com",
port: 465,
secure: true,
auth: {
user: "user@yourdomain.com",
pass: "your-password"
}
}
}
}
}
}
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfUserRecipient: &shared.UserRecipientParam{
Email: courier.String("sarah@acme-corp.com"),
},
},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
Providers: shared.MessageProvidersParam{
"smtp": shared.MessageProvidersTypeParam{
Override: map[string]any{
"config": map[string]any{
"host": "smtp.yourdomain.com",
"port": 465,
"secure": true,
"auth": map[string]any{
"user": "user@yourdomain.com",
"pass": "your-password",
},
},
},
},
},
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(UserRecipient.builder().email("sarah@acme-corp.com").build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.providers(MessageProviders.builder()
.putAdditionalProperty("smtp", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
"config", java.util.Map.of(
"host", "smtp.yourdomain.com",
"port", 465,
"secure", true,
"auth", java.util.Map.of(
"user", "user@yourdomain.com",
"pass", "your-password"
)
)
))))
.build())
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
'to' => [
'email' => 'sarah@acme-corp.com',
],
'providers' => [
'smtp' => [
'override' => [
'config' => [
'host' => 'smtp.yourdomain.com',
'port' => 465,
'secure' => true,
'auth' => [
'user' => 'user@yourdomain.com',
'pass' => 'your-password',
],
],
],
],
],
],
);
SendMessageParams parameters = new()
{
Message = new()
{
To = new UserRecipient { Email = "sarah@acme-corp.com" },
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
Providers = new Dictionary<string, MessageProvidersType>()
{
{
"smtp",
new()
{
Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"config": {
"host": "smtp.yourdomain.com",
"port": 465,
"secure": true,
"auth": {
"user": "user@yourdomain.com",
"pass": "your-password"
}
}
}
"""
),
}
},
},
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"email": "sarah@acme-corp.com"}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
--message.providers '{"smtp": {"override": {"config": {"host": "smtp.yourdomain.com", "port": 465, "secure": true, "auth": {"user": "user@yourdomain.com", "pass": "your-password"}}}}}'
With Courier MCP, send my template to sarah@acme-corp.com over implicit TLS on port 465.
| Provider | Host | Port | secure | requireTLS |
|---|---|---|---|---|
| Office 365 | smtp.office365.com | 587 | false | true |
| Gmail (app password) | smtp.gmail.com | 587 | false | true |
| On-prem Exchange | mail.yourdomain.com | 587 | false | true |
| Custom (implicit TLS) | varies | 465 | true | - |
| Option | Type | Description |
|---|---|---|
host | string | SMTP server hostname |
port | number | SMTP port (commonly 25, 465, 587) |
secure | boolean | Use SSL (true for port 465) |
requireTLS | boolean | Require STARTTLS (true for port 587) |
auth | object | Authentication credentials { user, pass } |
tls | object | TLS options (see NodeMailer TLS docs) |
connectionTimeout | number | Connection timeout in milliseconds |
socketTimeout | number | Socket timeout in milliseconds |
config override option.
Security best practices
- Use app-specific passwords for Office 365 and Gmail, not regular account passwords.
- Send from a dedicated service account, not a personal one.
- Store credentials in Courier Studio instead of passing them in every API request.
- Always use TLS: set
requireTLS: truefor port 587,secure: truefor port 465. - For HIPAA or data residency requirements, use a direct connection to your on-premises SMTP server.
Troubleshooting
Courier verifies your SMTP connection before each send. If verification fails, Courier does not send the message and returns an error. Courier retries connection timeouts (ETIMEDOUT) and temporary server unavailability.
Connection Timeouts
Connection Timeouts
- Check that firewall rules allow outbound connections to your SMTP server
- Check the SMTP host and port
- Check that your SMTP server is reachable from Courier’s infrastructure
Authentication Failures
Authentication Failures
- Check the username and password
- For Office 365 with MFA enabled, use an app-specific password
- Check that the account may send email over SMTP
TLS/SSL Errors
TLS/SSL Errors
- Check that your SMTP server supports the encryption method you requested
- Check certificate validity if you use custom certificates
- Check that
secureandrequireTLSmatch your server configuration
Provider details
smtp
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.