User Manual¶
Introduction¶
This document is meant to be an overview of all of the capabilities of Mailgun and how you can best leverage those capabilities. It is organized around these major features that Mailgun provides:
- Sending Messages
- Tracking Messages
- Receiving, Forwarding and Storing Messages
- Email Verification
- Inbox Placement
At the heart of Mailgun is the API. Most of the Mailgun service can be accessed through the RESTful HTTP API without the need to install any libraries. However, we have written Libraries for many popular languages. Be sure to check out the additional capabilities provided by using our libraries.
You can also access many Mailgun features through your Mailgun Control Panel using your browser and logging in at https://app.mailgun.com/app/dashboard.
In addition to the API, Mailgun supports the standard SMTP protocol. We have included some instructions on how to use Mailgun, via SMTP, at the end of the User Manual.
If you are anxious to get started right away, feel free to check out the Quickstart Guide or Mailgun API Reference. There are also FAQ and Email Best Practices that you can reference.
Finally, always feel free to contact our Support Team.
Getting Started¶
Verifying Your Domain¶
Each new Mailgun account is automatically provisioned with a sandbox domain
sandbox<uniq-alpha-numeric-string>@mailgun.org
. This domain is to be used
for testing only. It allows both sending and receiving messages; and also
tracking can be enabled for it. But it only allows sending to a list of up to 5
authorized recipients.
This limitation is also in effect for routes that are triggered by messages
addressed to the sandbox domain and mailing lists created under that domain.
To be able to use Mailgun in production a custom domain(s) has to be created and verified with Mailgun.
Verifying your domain is easy. Start by adding a domain or subdomain you own in
the Domains
tab of the Mailgun control panel. Next, add the two TXT DNS
records found in the Domain Verification & DNS section of the domain
settings page of the Mailgun control panel to your DNS provider:
- SPF: Sending server IP validation. Used by majority of email service providers. Learn about SPF.
- DKIM: Like SPF, but uses cryptographic methods for validation. Supported by many email service providers. This is the record that Mailgun references make sure that the domain actually belongs to you. Learn about DKIM
Once you’ve added the two TXT records and they’ve propagated, your domain
will be verified. In the Mailgun control panel verified domains are marked by a
green Verified
badge next to their name.
If it has been awhile since you have configured the DNS records but the domain
is still reported as Unverified
, then try pressing the Check DNS Records Now
button on the domain information page. If that does not help either, then
please create a support ticket.
Other DNS records
- CNAME DNS record with value mailgun.org, should be added if you want Mailgun to track clicks, opens, and unsubscribes.
- MX DNS records are required if you want Mailgun to receive and route/store
messages addressed to the domain recipients. You need to configure 2 MX
records with values
10 mxa.mailgun.org
and10 mxb.mailgun.org
. We recommend adding them even if you do not plan the domain to get inbound messages, because having MX DNS records configured may improve deliverability of messages sent from the domain. Learn about MX DNS records
Warning
Do not configure MX DNS records if you already have another provider handling inbound mail delivery for the domain.
DNS Records Summary
Type | Required | Purpose | Value |
---|---|---|---|
TXT | Yes | Domain verification (SPF) | v=spf1 include:mailgun.org ~all |
TXT | Yes | Domain verification (DKIM) | Find this record in “Domain Verification & DNS” section of the settings page for a particular domain in the Mailgun control panel. |
CNAME | Enables tracking | mailgun.org |
|
MX | Enables receiving | 10 mxa.mailgun.org |
|
MX | Enables receiving | 10 mxb.mailgun.org |
Common DNS Provider Documentation
Common providers are listed below. If yours is not listed, contact your DNS provider for assistance:
- GoDaddy: MX - CNAME - TXT
- NameCheap: All Records
- Network Solutions: MX - CNAME - TXT
- Rackspace Email & Apps: All Records
- Rackspace Cloud DNS: Developer Guide
- Amazon Route 53: Developer Guide
Managing User Roles¶
How to Manage
Role-based access control sets all current users to Admin-level users by default. To assign different roles to your account’s users, please visit the Account section of the control panel. There, you can choose the appropriate permissions level for each user. And when it’s time to add new users to your account, you’ll be able to easily select a role upon user creation.
Roles
Role | Description |
---|---|
Analyst | Analyst users are very limited. They have access to read most data, but can only modify their own settings. |
Billing | Billing users are focused on billing actions. Most of their access will be read only, billing is the only non-admin users who have access to:
|
Support | Support users are restricted in what they can edit. In addition to being able to read most data, they will be able to:
|
Developer | Developer users are highly trusted. This role can read and write almost all data. This includes everything support has, plus can:
Warning This role has access to read API Keys and SMTP credentials. This data is highly sensitive. |
Admin | Admin users have read and write access to everything. Only admins on the account can:
Note The account owner will always be an admin user. Warning This role has access to change API Keys and SMTP credentials. This data is highly sensitive. |
Sending Messages¶
There are two ways to send messages using Mailgun:
- HTTP API
- SMTP
Both methods work great and support the same feature set, so choose one based on your preferences and requirements.
Sending via API¶
When sending via HTTP API, Mailgun offers two options:
- You can send emails in MIME format, but this would require you to use a MIME building library for your programming language.
- You can submit the individual parts of your messages to Mailgun, such as text and html parts, attachments, and so on. This doesn’t require any MIME knowledge on your part.
Note
- Mailgun supports maximum messages size of 25MB.
- HTTP send will error with “parameter is not a valid address” if the provided email address fails syntax checks in accordance with RFC5321, RFC5322, RFC6854.
See sending messages section in our API Reference for a full list of message sending options.
Examples: sending messages via HTTP¶
Sending mails using Mailgun API is extremely simple: as simple as performing an HTTP POST request to an API URL.
Sending a plain text message:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Excited User <mailgun@YOUR_DOMAIN_NAME>' \
-F to=YOU@YOUR_DOMAIN_NAME \
-F to=bar@example.com \
-F subject='Hello' \
-F text='Testing some Mailgun awesomeness!'
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import com.mailgun.model.message.MessageResponse;
// ...
public MessageResponse sendSimpleMessage() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
Message message = Message.builder()
.from("Excited User <USER@YOURDOMAIN.COM>")
.to("artemis@example.com")
.subject("Hello")
.text("Testing out some Mailgun awesomeness!")
.build();
return mailgunMessagesApi.sendMessage(YOUR_DOMAIN_NAME, message);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>',
'to' => 'bob@example.com',
'subject' => 'Hello',
'text' => 'Testing some Mailgun awesomness!'
);
# Make the call to the client.
$mgClient->messages()->send($domain, $params);
def send_simple_message():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
data={"from": "Excited User <mailgun@YOUR_DOMAIN_NAME>",
"to": ["bar@example.com", "YOU@YOUR_DOMAIN_NAME"],
"subject": "Hello",
"text": "Testing some Mailgun awesomness!"})
def send_simple_message
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
:from => "Excited User <mailgun@YOUR_DOMAIN_NAME>",
:to => "bar@example.com, YOU@YOUR_DOMAIN_NAME",
:subject => "Hello",
:text => "Testing some Mailgun awesomness!"
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendSimpleMessageChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendSimpleMessage ().Content.ToString ());
}
public static IRestResponse SendSimpleMessage ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter ("from", "Excited User <mailgun@YOUR_DOMAIN_NAME>");
request.AddParameter ("to", "bar@example.com");
request.AddParameter ("to", "YOU@YOUR_DOMAIN_NAME");
request.AddParameter ("subject", "Hello");
request.AddParameter ("text", "Testing some Mailgun awesomness!");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func SendSimpleMessage(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mg.NewMessage(
"Excited User <mailgun@YOUR_DOMAIN_NAME>",
"Hello",
"Testing some Mailgun awesomeness!",
"YOU@YOUR_DOMAIN_NAME",
)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}
const API_KEY = 'YOUR_API_KEY';
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({username: 'api', key: API_KEY});
const messageData = {
from: 'Excited User <me@samples.mailgun.org>',
to: 'foo@example.com, bar@example.com',
subject: 'Hello',
text: 'Testing some Mailgun awesomeness!'
};
client.messages.create(DOMAIN, messageData)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
Sample response:
{
"message": "Queued. Thank you.",
"id": "<20111114174239.25659.5817@samples.mailgun.org>"
}
Sending a message with HTML and text parts. This example also attaches two files to the message:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Excited User <YOU@YOUR_DOMAIN_NAME>' \
-F to='foo@example.com' \
-F cc='bar@example.com' \
-F bcc='baz@example.com' \
-F subject='Hello' \
-F text='Testing some Mailgun awesomness!' \
--form-string html='<html>HTML version of the body</html>' \
-F attachment=@files/cartman.jpg \
-F attachment=@files/cartman.png
import java.io.File;
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import com.mailgun.model.message.MessageResponse;
// ...
public MessageResponse sendComplexMessage() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
Message message = Message.builder()
.from("Excited User <USER@YOURDOMAIN.COM>")
.to("alice@example.com")
.cc("bob@example.com")
.bcc("joe@example.com")
.subject("Hello")
.html("<html>HTML version </html>")
.attachment(new File("/temp/folder/test.txt"))
.build();
return mailgunMessagesApi.sendMessage(YOUR_DOMAIN_NAME, message);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>',
'to' => 'bob@example.com',
'cc' => 'alice@example.com',
'bcc' => 'john@example.com',
'subject' => 'Hello',
'text' => 'Testing some Mailgun awesomness!',
'html' => '<html>HTML version of the body</html>',
'attachment' => array(
array(
'filePath' => 'test.txt',
'filename' => 'test_file.txt'
)
)
);
# Make the call to the client.
$result = $mgClient->messages()->send($domain, $params);
def send_complex_message():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
files=[("attachment", ("test.jpg", open("files/test.jpg","rb").read())),
("attachment", ("test.txt", open("files/test.txt","rb").read()))],
data={"from": "Excited User <YOU@YOUR_DOMAIN_NAME>",
"to": "foo@example.com",
"cc": "baz@example.com",
"bcc": "bar@example.com",
"subject": "Hello",
"text": "Testing some Mailgun awesomness!",
"html": "<html>HTML version of the body</html>"})
def send_complex_message
data = {}
data[:from] = "Excited User <YOU@YOUR_DOMAIN_NAME>"
data[:to] = "foo@example.com"
data[:cc] = "baz@example.com"
data[:bcc] = "bar@example.com"
data[:subject] = "Hello"
data[:text] = "Testing some Mailgun awesomness!"
data[:html] = "<html>HTML version of the body</html>"
data[:attachment] = []
data[:attachment] << File.new(File.join("files", "test.jpg"))
data[:attachment] << File.new(File.join("files", "test.txt"))
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages", data
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendComplexMessageChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendComplexMessage ().Content.ToString ());
}
public static IRestResponse SendComplexMessage ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter ("from", "Excited User <YOU@YOUR_DOMAIN_NAME>");
request.AddParameter ("to", "foo@example.com");
request.AddParameter ("cc", "baz@example.com");
request.AddParameter ("bcc", "bar@example.com");
request.AddParameter ("subject", "Hello");
request.AddParameter ("text", "Testing some Mailgun awesomness!");
request.AddParameter ("html",
"<html>HTML version of the body</html>");
request.AddFile ("attachment", Path.Combine ("files", "test.jpg"));
request.AddFile ("attachment", Path.Combine ("files", "test.txt"));
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func SendComplexMessage(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mg.NewMessage(
"Excited User <YOU@YOUR_DOMAIN_NAME>",
"Hello",
"Testing some Mailgun awesomeness!",
"foo@example.com",
)
m.AddCC("baz@example.com")
m.AddBCC("bar@example.com")
m.SetHtml("<html>HTML version of the body</html>")
m.AddAttachment("files/test.jpg")
m.AddAttachment("files/test.txt")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}
import path from 'node:path';
import fs from 'node:fs/promises';
const API_KEY = 'YOUR_API_KEY';
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: API_KEY });
(async () => {
const filepath = path.resolve('sample.jpg');
try {
const file = {
filename: 'sample.jpg',
data: await fs.readFile(filepath)
};
const attachment = [file];
const data = {
from: 'Excited User <me@samples.mailgun.org>',
to: ['foo@example.com', 'baz@example.com', 'bar@example.com'],
cc: 'baz@example.com',
bcc: 'bar@example.com',
subject: 'Complex',
text: 'Testing some Mailgun awesomness!',
html: '<html>HTML version of the body</html>',
attachment
};
const result = await client.messages.create(DOMAIN, data);
console.log(result);
} catch (error) {
console.error(error);
}
})();
Sending a MIME message which you pre-build yourself using a MIME library of your choice:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages.mime \
-F to='bob@example.com' \
-F message=@files/message.mime
import java.io.File;
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.client.MailgunClient;
import com.mailgun.model.message.MailgunMimeMessage;
import com.mailgun.model.message.MessageResponse;
// ...
public MessageResponse sendMIMEMessage() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
MailgunMimeMessage mailgunMimeMessage = MailgunMimeMessage.builder()
.to("megan@example.com)
.message(new File("/path/to/file.mime"))
.build();
return mailgunMessagesApi.sendMIMEMessage(YOUR_DOMAIN_NAME, mailgunMimeMessage);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$recipients = array(
'bob@example.com',
'alice@example.com',
'john@example.com';
);
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>'
);
$mime_string = '<Pass fully formed MIME string here>'
# Make the call to the client.
$result = $mgClient->messages()->sendMime($domain, $recipients, $mime_string, $params);
def send_mime_message():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages.mime",
auth=("api", "YOUR_API_KEY"),
data={"to": "bar@example.com"},
files={"message": open("files/message.mime")})
def send_mime_message
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages.mime",
:to => "bar@example.com",
:message => File.new(File.join("files", "message.mime"))
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendMimeMessageChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendMimeMessage ().Content.ToString ());
}
public static IRestResponse SendMimeMessage ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages.mime";
request.AddParameter ("to", "bar@example.com");
request.AddFile ("message", Path.Combine ("files", "message.mime"));
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"os"
"time"
)
func SendMimeMessage(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
mimeMsgReader, err := os.Open("files/message.mime")
if err != nil {
return "", err
}
m := mg.NewMIMEMessage(mimeMsgReader, "bar@example.com")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
const API_KEY = 'YOUR_API_KEY';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
import MailComposer from 'nodemailer/lib/mail-composer';
const mailgun = new Mailgun(formData);
const mg = mailgun.client({ username: 'api', key: API_KEY });
(async () => {
const mailOptions = {
from: 'YOU@YOUR_DOMAIN_NAME',
to: 'bob@example.com',
subject: 'Hello',
text: 'Testing some Mailgun awesomeness!'
};
try {
const mail = new MailComposer(mailOptions);
const compiledMessage = await mail.compile().build();
const res = await mg.messages.create(DOMAIN, {
to: 'bob@example.com',
message: compiledMessage
});
console.log(res);
} catch (error) {
console.error(error);
}
})();
An example of how to toggle tracking on a per-message basis. Note the o:tracking
option. This will disable link rewriting for this message:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Sender Bob <sbob@YOUR_DOMAIN_NAME>' \
-F to='alice@example.com' \
-F subject='Hello' \
-F text='Testing some Mailgun awesomness!' \
-F o:tracking=False
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import com.mailgun.model.message.MessageResponse;
// ...
public MessageResponse sendMessageNoTracking() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
Message message = Message.builder()
.from("Excited User <USER@YOURDOMAIN.COM>")
.to("alice@example.com")
.subject("Hello")
.text("Testing out some Mailgun awesomeness!")
.tracking(false)
.build();
return mailgunMessagesApi.sendMessage(YOUR_DOMAIN_NAME, message);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>',
'to' => 'foo@example.com',
'subject' => 'Hello',
'text' => 'Testing some Mailgun awesomness!',
'o:tracking' => false
);
# Make the call to the client.
$result = $mgClient->messages()->send($domain, $params);
def send_message_no_tracking():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
data={"from": "Excited User <YOU@YOUR_DOMAIN_NAME>",
"to": ["bar@example.com", "baz@example.com"],
"subject": "Hello",
"text": "Testing some Mailgun awesomness!",
"o:tracking": False})
def send_message_no_tracking
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
:from => "Excited User <YOU@YOUR_DOMAIN_NAME>",
:to => "bar@example.com, baz@example.com",
:subject => "Hello",
:text => "Testing some Mailgun awesomness!",
"o:tracking" => false
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendMessageNoTrackingChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendMessageNoTracking ().Content.ToString ());
}
public static IRestResponse SendMessageNoTracking ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter ("from", "Excited User <YOU@YOUR_DOMAIN_NAME>");
request.AddParameter ("to", "bar@example.com");
request.AddParameter ("to", "baz@example.com");
request.AddParameter ("subject", "Hello");
request.AddParameter ("text", "Testing some Mailgun awesomness!");
request.AddParameter ("o:tracking", false);
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func SendMessageNoTracking(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mg.NewMessage(
"Excited User <YOU@YOUR_DOMAIN_NAME>",
"Hello",
"Testing some Mailgun awesomeness!",
"foo@example.com",
)
m.SetTracking(false)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}
const API_KEY = 'YOUR_API_KEY';
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({username: 'api', key: API_KEY});
const messageData = {
from: 'Excited User <me@samples.mailgun.org>',
to: 'alice@example.com',
subject: 'Hello',
text: 'Testing some Mailgun awesomeness!',
'o:tracking': 'False'
};
client.messages.create(YOUR_DOMAIN_NAME, messageData)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
An example of how to set message delivery time using the o:deliverytime
option:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Sender Bob <sbob@YOUR_DOMAIN_NAME>' \
-F to='alice@example.com' \
-F subject='Hello' \
-F text='Testing some Mailgun awesomness!' \
-F o:deliverytime='Fri, 14 Oct 2011 23:10:10 -0000'
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import com.mailgun.model.message.MessageResponse;
import java.time.ZonedDateTime;
// ...
public MessageResponse sendScheduledMessage() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
Message message = Message.builder()
.from("Excited User <USER@YOURDOMAIN.COM>")
.to("bruce@example.com")
.subject("Hello")
.text("Testing out some Mailgun awesomeness!")
.deliveryTime(ZonedDateTime.now().plusHours(2L)) // Two hours delay.
.build();
return mailgunMessagesApi.sendMessage(YOUR_DOMAIN_NAME, message);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>',
'to' => 'bob@example.com',
'subject' => 'Hello',
'text' => 'Testing some Mailgun awesomness!',
'o:deliverytime' => 'Wed, 01 Jan 2020 09:00:00 -0000'
);
# Make the call to the client.
$result = $mgClient->messages()->send($domain, $params);
def send_scheduled_message():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
data={"from": "Excited User <YOU@YOUR_DOMAIN_NAME>",
"to": "bar@example.com",
"subject": "Hello",
"text": "Testing some Mailgun awesomness!",
"o:deliverytime": "Fri, 25 Oct 2011 23:10:10 -0000"})
def send_scheduled_message
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
:from => "Excited User <YOU@YOUR_DOMAIN_NAME>",
:to => "bar@example.com",
:subject => "Hello",
:text => "Testing some Mailgun awesomeness!",
"o:deliverytime" => "Fri, 25 Oct 2011 23:10:10 -0000"
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendScheduledMessageChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendScheduledMessage ().Content.ToString ());
}
public static IRestResponse SendScheduledMessage ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter ("from", "Excited User <YOU@YOUR_DOMAIN_NAME>");
request.AddParameter ("to", "bar@example.com");
request.AddParameter ("subject", "Hello");
request.AddParameter ("text", "Testing some Mailgun awesomness!");
request.AddParameter ("o:deliverytime",
"Fri, 14 Oct 2011 23:10:10 -0000");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func SendScheduledMessage(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mg.NewMessage(
"Excited User <YOU@YOUR_DOMAIN_NAME>",
"Hello",
"Testing some Mailgun awesomeness!",
"bar@example.com",
)
m.SetDeliveryTime(time.Now().Add(5 * time.Minute))
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}
const API_KEY = 'YOUR_API_KEY';
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({username: 'api', key: API_KEY});
const messageData = {
from: 'Excited User <me@samples.mailgun.org>',
to: 'alice@example.com',
subject: 'Hello',
text: 'Testing some Mailgun awesomeness!',
"o:deliverytime": 'Fri, 6 Jul 2017 18:10:10 -0000'
};
client.messages.create(YOUR_DOMAIN_NAME, messageData)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
An example of how to tag a message with the o:tag
option:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Sender Bob <sbob@YOUR_DOMAIN_NAME>' \
-F to='alice@example.com' \
-F subject='Hello' \
-F text='Testing some Mailgun awesomness!' \
-F o:tag='September newsletter' \
-F o:tag='newsletters'
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import com.mailgun.model.message.MessageResponse;
// ...
public MessageResponse sendTaggedMessage() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
Message message = Message.builder()
.from("Excited User <USER@YOURDOMAIN.COM>")
.to("bruce@example.com")
.subject("Hello")
.text("Testing out some Mailgun awesomeness!")
.tag("newsletters")
.tag("September newsletter")
.build();
return mailgunMessagesApi.sendMessage(YOUR_DOMAIN_NAME, message);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>',
'to' => 'bob@example.com',
'subject' => 'Hello',
'text' => 'Testing some Mailgun awesomness!',
'o:tag' => array('Tag1', 'Tag2', 'Tag3')
);
# Make the call to the client.
$result = $mgClient->messages()->send($domain, $params);
def send_tagged_message():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
data={"from": "Excited User <YOU@YOUR_DOMAIN_NAME>",
"to": "bar@example.com",
"subject": "Hello",
"text": "Testing some Mailgun awesomness!",
"o:tag": ["September newsletter", "newsletters"]})
def send_tagged_message
data = {}
data[:from] = "Excited User <YOU@YOUR_DOMAIN_NAME>"
data[:to] = "bar@example.com"
data[:subject] = "Hello"
data[:text] = "Testing some Mailgun awesomness!"
data["o:tag"] = []
data["o:tag"] << "September newsletter"
data["o:tag"] << "newsletters"
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages", data
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendTaggedMessageChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendTaggedMessage ().Content.ToString ());
}
public static IRestResponse SendTaggedMessage ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter ("from", "Excited User <YOU@YOUR_DOMAIN_NAME>");
request.AddParameter ("to", "bar@example.com");
request.AddParameter ("subject", "Hello");
request.AddParameter ("text", "Testing some Mailgun awesomness!");
request.AddParameter ("o:tag", "September newsletter");
request.AddParameter ("o:tag", "newsletters");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func SendTaggedMessage(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mg.NewMessage(
"Excited User <YOU@YOUR_DOMAIN_NAME>",
"Hello",
"Testing some Mailgun awesomeness!",
"bar@example.com",
)
err := m.AddTag("FooTag", "BarTag", "BlortTag")
if err != nil {
return "", err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}
const API_KEY = 'YOUR_API_KEY';
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({username: 'api', key: API_KEY});
const messageData = {
from: 'Excited User <me@samples.mailgun.org>',
to: 'alice@example',
subject: 'Tagged',
text: 'Testing some Mailgun awesomeness!',
"o:tag" : ['newsletters', 'September newsletter']
};
client.messages.create(YOUR_DOMAIN_NAME, messageData)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
An example of how to send a message with custom connection settings:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Sender Bob <sbob@YOUR_DOMAIN_NAME>' \
-F to='alice@example.com' \
-F subject='Hello' \
-F text='Testing some Mailgun awesomness!' \
-F o:require-tls=True \
-F o:skip-verification=False
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import com.mailgun.model.message.MessageResponse;
// ...
public MessageResponse sendConnection() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
Message message = Message.builder()
.from("Excited User <USER@YOURDOMAIN.COM>")
.to("alice@example.com")
.to("bob@example.com")
.subject("Hello")
.text("Testing out some Mailgun awesomeness!")
.requireTls(true)
.skipVerification(false)
.build();
return mailgunMessagesApi.sendMessage(YOUR_DOMAIN_NAME, message);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>',
'to' => 'bob@example.com',
'subject' => 'Hello',
'text' => 'Testing some Mailgun awesomness!',
'html' => '<html>HTML version of the body</html>',
'o:require-tls' => true,
'o:skip-verification' => false
);
# Make the call to the client.
$result = $mgClient->messages()->send($domain, $params);
def send_require_tls():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
data={"from": "Excited User <YOU@YOUR_DOMAIN_NAME>",
"to": ["bar@example.com", "baz@example.com"],
"subject": "Hello",
"text": "Testing some Mailgun awesomness!",
"o:require-tls": True,
"o:skip-verification": False})
def send_require_tls
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
:from => "Excited User <YOU@YOUR_DOMAIN_NAME>",
:to => "bar@example.com, baz@example.com",
:subject => "Hello",
:text => "Testing some Mailgun awesomness!",
"o:require-tls" => true,
"o:skip-verification" => false
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendConnectionChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendWithTLS ().Content.ToString ());
}
public static IRestResponse SendWithTLS ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter ("from", "Excited User <YOU@YOUR_DOMAIN_NAME>");
request.AddParameter ("to", "bar@example.com");
request.AddParameter ("to", "baz@example.com");
request.AddParameter ("subject", "Hello");
request.AddParameter ("text", "Testing some Mailgun awesomness!");
request.AddParameter ("o:require-tls", true);
request.AddParameter ("o:skip-verification", false);
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func SendWithConnectionOptions(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mg.NewMessage(
"Excited User <YOU@YOUR_DOMAIN_NAME>",
"Hello",
"Testing some Mailgun awesomeness!",
"foo@example.com",
)
m.SetRequireTLS(true)
m.SetSkipVerification(true)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}
const API_KEY = 'YOUR_API_KEY';
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({username: 'api', key: API_KEY});
const messageData = {
from: 'Excited User <me@samples.mailgun.org>',
to: 'alice@example.com',
subject: 'Hello',
text: 'Testing some Mailgun awesomeness!',
'o:require-tls': 'True',
'o:skip-verification': 'False'
};
client.messages.create(YOUR_DOMAIN_NAME, messageData)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
Sending Inline Images¶
Mailgun assigns content-id to each image passed via inline
API parameter, so
it can be referenced in HTML part.
Example of sending inline image. Note how image is referenced in HTML part simply by the filename:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Excited User <YOU@YOUR_DOMAIN_NAME>' \
-F to='alice@example.com' \
-F subject='Hello' \
-F text='Testing some Mailgun awesomness!' \
--form-string html='<html>Inline image here: <img src="cid:cartman.jpg"></html>' \
-F inline=@files/cartman.jpg
import java.io.File;
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import com.mailgun.model.message.MessageResponse;
// ...
public MessageResponse sendInlineImage() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
Message message = Message.builder()
.from("Excited User <USER@YOURDOMAIN.COM>")
.to("alice@example.com")
.to("bob@example.com")
.cc("joe@example.com")
.subject("Hello")
.html("<html>Inline image here: <img src=\"cid:test.jpg\"></html>")
.inline(new File("/path/to/test.jpg"))
.build();
return mailgunMessagesApi.sendMessage(YOUR_DOMAIN_NAME, message);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>',
'to' => 'bob@example.com',
'subject' => 'Hello',
'text' => 'Testing some Mailgun awesomness!',
'html' => '<html>Inline image: <img src="cid:test.jpg"></html>',
'inline' => array(
array('filePath' => '/path/to/test.jpg')
)
);
# Make the call to the client.
$result = $mgClient->messages()->send($domain, $params);
def send_inline_image():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
files=[("inline", open("files/test.jpg"))],
data={"from": "Excited User <YOU@YOUR_DOMAIN_NAME>",
"to": "bar@example.com",
"subject": "Hello",
"text": "Testing some Mailgun awesomness!",
"html": '<html>Inline image here: <img src="cid:test.jpg"></html>'})
def send_inline_image
data = {}
data[:from] = "Excited User <YOU@YOUR_DOMAIN_NAME>"
data[:to] = "bar@example.com"
data[:subject] = "Hello"
data[:text] = "Testing some Mailgun awesomness!"
data[:html] = '<html>Inline image here: <img src="cid:test.jpg"></html>'
data[:inline] = File.new(File.join("files", "test.jpg"))
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages", data
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendInlineImageChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendInlineImage ().Content.ToString ());
}
public static IRestResponse SendInlineImage ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter ("from", "Excited User <YOU@YOUR_DOMAIN_NAME>");
request.AddParameter ("to", "baz@example.com");
request.AddParameter ("subject", "Hello");
request.AddParameter ("text", "Testing some Mailgun awesomness!");
request.AddParameter ("html",
"<html>Inline image here: <img src=\"cid:test.jpg\"></html>");
request.AddFile ("inline", "files/test.jpg");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func SendInlineImage(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mg.NewMessage(
"Excited User <YOU@YOUR_DOMAIN_NAME>",
"Hello",
"Testing some Mailgun awesomeness!",
"foo@example.com",
)
m.AddCC("baz@example.com")
m.AddBCC("bar@example.com")
m.SetHtml(`<html>Inline image here: <img alt="image" src="cid:test.jpg"/></html>`)
m.AddInline("files/test.jpg")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
import path from 'node:path';
import fs from 'node:fs/promises';
const mailgun = new Mailgun(formData);
const filepath = path.resolve('./test.jpg');
const messageData = {
from: 'Excited User <me@samples.mailgun.org>',
to: 'foo@example.com, baz@example.com, bar@example.com',
subject: 'Hello',
html: '<html>Inline image here: <img alt="image" id="1" src="cid:test.jpg"/></html> Some extra text'
};
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
fs.readFile(filepath)
.then((data) => {
const file = {
filename: 'test.jpg',
data
};
messageData.inline = file;
return client.messages.create(DOMAIN, messageData);
})
.then((response) => {
console.log(response);
});
Sending via SMTP¶
Mailgun supports sending via SMTP. Our servers listen on ports 25
, 465
, 587
, and 2525
. Port 465 requires a TLS connection. Ports 25
, 587
, and 2525
require a non-TLS connection but may be upgraded to TLS using the STARTTLS
command.
Note
- Some ISPs are blocking or throttling SMTP port 25. We recommend using #587 instead.
- Google Compute Engine allows port
2525
for SMTP submission. - SMTP send will error with “cannot parse to address” or “cannot parse from address” if the provided email address fails syntax checks in accordance with RFC5321, RFC5322, RFC6854.
Warning
IP addresses for HTTP and SMTP API endpoints will change frequently and subjected to change without notice. Ensure there are no IP-based ACLs that would prevent communication to new IP addresses that may be added or removed at any time.
Use “plain text” SMTP authentication and the credentials from the domain details page in your Control Panel which can be found by clicking on a domain in the Domains Tab. For enhanced security, use TLS encryption.
Note
See SMTP to learn how to configure the most popular SMTP software and email clients to work with Mailgun
Passing Sending Options
When sending a message via SMTP you can pass additional sending options via custom MIME headers listed in the table below.
Header Description X-Mailgun-Tag Tag string used for aggregating stats. See Tagging for more information. You can mark a message with several categories by setting multiple X-Mailgun-Tag
headers.X-Mailgun-Dkim Enables/disables DKIM signatures on per-message basis. Use yes
orno
.X-Mailgun-Deliver-By Desired time of delivery. See Scheduling Delivery and Date Format. X-Mailgun-Drop-Message Enables sending in test mode. Pass yes
if needed. See Sending in Test Mode.X-Mailgun-Track Toggles tracking on a per-message basis, see Tracking Messages for details. Pass yes
orno
.X-Mailgun-Track-Clicks Toggles clicks tracking on a per-message basis. Has higher priority than domain-level setting. Pass yes
orno
.X-Mailgun-Track-Opens Toggles opens tracking on a per-message basis. Has higher priority than domain-level setting. Pass yes
orno
.X-Mailgun-Sending-Ip Used to specify an IP Address to send the email with that is owned by your account. X-Mailgun-Sending-Ip-Pool If an IP Pool ID is provided, the email will be delivered with an IP that belongs in that pool. X-Mailgun-Require-TLS Use this header to control TLS connection settings. See TLS Sending Connection Settings X-Mailgun-Skip-Verification Use this header to control TLS connection settings. See TLS Sending Connection Settings X-Mailgun-Recipient-Variables Use this header to substitute recipient variables referenced in a batched mail message. See Batch Sending X-Mailgun-Variables Use this header to attach a custom JSON data to the message. See Attaching Data to Messages for more information. X-Mailgun-Delivery-Time-Optimize-Period Toggles STO on a per-message basis. String should be set to the number of hours in [0- 9]+h format. See Sending a message with STO for details. X-Mailgun-Time-Zone-Localize Toggles TZO on a per-message basis. String should be set to preferred delivery time in HH:mm or hh:mmaa format, where HH:mm is used for 24 hour format without AM/PM and hh:mmaa is used for 12 hour format with AM/PM. See Sending a message with TZO for details.
Sending a message with STO¶
Mailgun’s Send Time Optimization (STO) feature uses machine learning to analyze engagement data (opens and clicks) for a recipient to determine when a user is most engaged with their messages. If we have enough engagement data to make a determination of when a user is most engaged, Mailgun will hold onto the message and deliver it during that optimal period. The idea here is that if we can deliver a message to a user when they are most engaged, the message will be towards the top and is more likely to be engaged with.
You can send a message using STO via API by passing in the parameter o:deliverytime-optimize-period
or via SMTP using the MIME header X-Mailgun-Delivery-Time-Optimize-Period
. The value should be a string in the [0-9]+h
format. This value defines the window in which Mailgun will run the optimization algorithm against the data we have and deliver the message. We recommend using a minimum value of 24h
for best results, and the max value is 72h
.
Please note that STO is only available on certain plans. See www.mailgun.com/pricing for more info.
Sending a message with TZO¶
Mailgun’s Timezone Optimization feature allows senders to schedule messages to be delivered in a recipient’s local timezone. Similar to how our message scheduling works, with TZO you pass your desired delivery time, and Mailgun will convert that to a user’s local timezone, if we have data for that recipient. If we do not have data for that recipient, the message will be delivered immediately.
Timezones are estimated based on a user’s IP address. Mailgun collects the IP address on click events, and uses a geolocation service to translate the IP address into a timezone for the user. We store that timezone in a database (hashed, of course), so that when TZO is used on a message, Mailgun will look up the timezone for that user, and schedule the message for the delivery time in that user’s local timezone.
You can send a message using TZO via API by passing in the parameter o:time-zone-localize
or via SMTP using the MIME header X-Mailgun-Time-Zone-Localize
. The value (String) should be set to preferred delivery time in HH:mm or hh:mmaa format, where HH:mm is used for 24 hour format without AM/PM and hh:mmaa is used for 12 hour format with AM/PM.
Please note that TZO is only available on certain plans. See www.mailgun.com/pricing for more info.
Sending an AMP message¶
Google’s Accelerated Mobile Pages (AMP) for Email allows senders to include a subset of AMP components within an email message. This lets recipients receive content rich, dynamic emails, that result in a more interactive experience with email messages. For recipients, this means the ability to view real time inventory, submit updates or replies in a doc, respond to surveys, etc., all from directly within the email message. For marketers, this could mean improved conversion rates since users can interact with you directly from the email without visiting a website!
AMP Requirements
While AMP is a really exciting email tool, it takes a bit of setup before you can successfully send an AMP email message to your recipients.
Registration
In order to send AMP emails to mailboxes that support it (Gmail for now), you’ll need to register your sending domain with Google.
Content
Your AMP email content must comply with Google’s requirements. First, you need to ensure that you’re following Google’s Bulk Senders Guidelines.
Next, It’s important to follow the Amp for Email specification when building your AMP messages, specifically the required markup, AMP components, and CSS requirements. One of the gotchas you may run into, for example, is the <img> tag is replaced with <amp-img>. As you go along, you can use Gmail’s AMP for Email Playground to test whether your message content will pass the validation process.
HTTPS URLs
All URLs must use HTTPS, including tracking and unsubscribe URLs. If you’re using Mailgun for your open/click tracking and unsubscribe URLs, you’ll need to follow these steps to enable HTTPS on your Mailgun tracking URLs.
Sending an AMP email on Mailgun
Mailgun has made it easy to send an AMP email using our API by providing the optional amp-html
parameter along with your AMP content. Mailgun will take care of building the proper text/x-amp-html
MIME portion. As long as you’re following the AMP requirements set by Google, you should be well on your way to sending your AMP messages.
Testing your email messages
If you’re waiting on Google for your domain to be registered, you can still start building AMP emails and testing them. Visit your Gmail settings page (GSuite users will need their admins to enable the Dynamic Email option), and then under the Dynamic Email section, check the box to Enable dynamic email. After that, click on Developer settings and enter your sending address in the field in order to whitelist your sending address, then click OK. As long as you’re following the proper requirements for sending AMP messages, you should be able to successfully receive an AMP email from your sending address to your Gmail account.
AMP Best Practices
MIME part organization
Some email clients will only render the last MIME part, so you should ensure your MIME parts are in the following order:
text/plain
text/x-amp-html
text/html
If you send a message via our API using the amp-html
, Mailgun will take care of the proper ordering.
Text / HTML fallback
Just like when you send an HTML email, you should provide a Text version as a fallback in case the recipient blocks HTML content. The same applies when sending AMP emails. If you send an AMP email to a recipient that doesn’t support it or has blocked the content, they can still view the message in another format.
Replying to and forwarding AMP messages
It’s important to note that email clients will strip out the text/x-amp-html
MIME in your messages when you reply to or forward the message. This is another reason why you should ensure you have text and HTML versions as a fallback when you send your emails.
Message Queue¶
When you submit messages for delivery Mailgun places them in a message queue.
- You can submit a large amount of messages and Mailgun will automatically queue the delivery in compliance with the receiving domains’ guidelines and maximum allowed sending rate optimized for each ESP (email service provider) such as Yahoo, GMail, etc.
- The Queue is dynamic so as you send more messages, your sending rates will increase, assuming you are sending quality traffic. (See Email Best Practices about warming up IP addresses.) Do not get discouraged if your messages take longer to be delivered at the beginning. As your reputation grows, your sending rate will grow too.
The queueing algorithms are one of the most important features of Mailgun. If you try to send bulk mailings all at once, most ISPs will block you, or worse, just drop your messages without telling you. In addition, it is important to gradually increase your sending rates according to many factors, including consistency of traffic, IP address sending history, and domain reputation.
Batch Sending¶
Mailgun supports the ability send to a group of recipients through a single API call or SMTP session. This is achieved by either:
- Using Batch Sending by specifying multiple recipient email addresses as
to
parameters and using Recipient Variables. - Using Mailing Lists with Template Variables.
Warning
It is important when using Batch Sending to also use Recipient Variables. This tells Mailgun to send each recipient an individual email with only their email in the to
field. If they are not used, all recipients’ email addresses will show up in the to
field for each recipient.
Recipient Variables
Recipient Variables are custom variables that you define, which you can then reference in the message body. They give you the ability to send a custom message to each recipient while still using a single API Call (or SMTP session).
To access a recipient variable within your email, simply reference %recipient.yourkey%
. For example, consider the following JSON:
{
"user1@example.com" : {"unique_id": "ABC123456789"},
"user2@example.com" : {"unique_id": "ZXY987654321"}
}
To reference the above variables within your email, use %recipient.unique_id%
.
Recipient Variables allow you to:
- Submit a message template;
- Include multiple recipients; and
- Include a set of key:value pairs with unique data for each recipient.
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Excited User <YOU@YOUR_DOMAIN_NAME>' \
-F to=alice@example.com \
-F to=bob@example.com \
-F recipient-variables='{"bob@example.com": {"first":"Bob", "id":1}, "alice@example.com": {"first":"Alice", "id": 2}}' \
-F subject='Hey, %recipient.first%' \
-F text='If you wish to unsubscribe, click http://mailgun/unsubscribe/%recipient.id%'
import java.util.List;
import java.util.Map;
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import com.mailgun.model.message.MessageResponse;
// ...
public MessageResponse sendTemplateMessage() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
Map<String, Object> aliceVars = Map.of(
"name", "Alice",
"id", 1
);
Map<String, Object> bobVars = Map.of(
"name", "Bob",
"id", 2
);
Map<String, Map<String, Object>> recipientVariables = Map.ofEntries(
Map.entry("alice@example.com", aliceVars),
Map.entry("bob@example.com", bobVars)
);
Message message = Message.builder()
.from("Excited User <USER@YOURDOMAIN.COM>")
.to(List.of("alice@example.com", "bob@example.com"))
.subject("Hey %recipient.name%")
.text("If you wish to unsubscribe, click <https://mailgun.com/unsubscribe/%recipient.id%>")
.recipientVariables(recipientVariables)
.build();
return mailgunMessagesApi.sendMessage(YOUR_DOMAIN_NAME, message);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>',
'to' => array('bob@example.com, alice@example.com'),
'subject' => 'Hey %recipient.first%',
'text' => 'If you wish to unsubscribe, click http://example.com/unsubscribe/%recipient.id%',
'recipient-variables' => '{"bob@example.com": {"first":"Bob", "id":1},
"alice@example.com": {"first":"Alice", "id": 2}}'
);
# Make the call to the client.
$result = $mgClient->messages()-send($domain, $params);
def send_template_message():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
data={"from": "Excited User <YOU@YOUR_DOMAIN_NAME>",
"to": ["alice@example.com, bob@example.com"],
"subject": "Hey, %recipient.first%",
"text": "If you wish to unsubscribe, click http://mailgun/unsubscribe/%recipient.id%'",
"recipient-variables": ('{"bob@example.com": {"first":"Bob", "id":1}, '
'"alice@example.com": {"first":"Alice", "id": 2}}')})
def send_template_message
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
:from => "Excited User <YOU@YOUR_DOMAIN_NAME>",
:to => "alice@example.com, bob@example.com",
:subject => "Hey, %recipient.first%",
:text => "If you wish to unsubscribe, click http://mailgun/unsubscribe/%recipient.id%'",
:'recipient-variables' => '{"bob@example.com": {"first":"Bob", "id":1}, "alice@example.com": {"first":"Alice", "id": 2}}'
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendTemplateMessageChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendTemplateMessage ().Content.ToString ());
}
public static IRestResponse SendTemplateMessage ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter ("from", "Excited User <YOU@YOUR_DOMAIN_NAME>");
request.AddParameter ("to", "alice@example.com");
request.AddParameter ("to", "bob@example.com");
request.AddParameter ("subject", "Hey, %recipient.first%");
request.AddParameter ("text",
"If you wish to unsubscribe, click http://mailgun/unsubscribe/%recipient.id%'");
request.AddParameter ("recipient-variables",
"{\"bob@example.com\": {\"first\":\"Bob\", \"id\":1}, \"alice@example.com\": {\"first\":\"Alice\", \"id\": 2}}");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func SendTemplateMessage(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mg.NewMessage(
"Excited User <YOU@YOUR_DOMAIN_NAME>",
"Hey %recipient.first%",
"If you wish to unsubscribe, click http://mailgun/unsubscribe/%recipient.id%",
) // IMPORTANT: No To:-field recipients!
m.AddRecipientAndVariables("bob@example.com", map[string]interface{}{
"first": "bob",
"id": 1,
})
m.AddRecipientAndVariables("alice@example.com", map[string]interface{}{
"first": "alice",
"id": 2,
})
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}
const API_KEY = 'YOUR_API_KEY';
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({username: 'api', key: API_KEY});
const data = {
from: 'Excited User <me@samples.mailgun.org>',
to: ['alice@example.com', 'bob@example.com'],
subject: 'Hey %recipient.name%',
text: 'If you wish to unsubscribe, click http://mailgun/unsubscribe/%recipient.recipientId%',
'recipient-variables': JSON.stringify({
'alice@example.com': {
name: 'Alice',
recipientId: 1
},
'bob@example.com':
{
name: 'Bob',
recipientId: 2
}
})
};
client.messages.create(DOMAIN, data)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
Note
The maximum number of recipients allowed for Batch Sending is 1,000.
Note
Recipient variables should be set as a valid JSON-encoded dictionary, where key is a plain recipient address and value is a dictionary with variables.
In the example above, Alice and Bob both will get personalized subject lines “Hey, Alice” and “Hey, Bob” and unique unsubscribe links.
When sent via SMTP, recipient variables can be included by adding the following header to
your email, X-Mailgun-Recipient-Variables: {"user1@example.com" : {"unique_id": "ABC123456789"}}
Example:
X-Mailgun-Recipient-Variables: {"bob@example.com": {"first":"Bob", "id":1}, "alice@example.com": {"first":"Alice", "id": 2}}
From: me@example.com
To: %recipient%
Date: 29 Mar 2016 00:23:35 -0700
Subject: Hello, %recipient.first%!
Message-Id: <20160329071939.35138.9413.6915422C@example.com>
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: quoted-printable
Hi, %recipient.first%,
=20
Please review your profile at example.com/orders/%recipient.id%.
=20
Thanks,
Example.com Team
Note
The value of the “X-Mailgun-Recipient-Variables” header should be valid JSON string, otherwise Mailgun won’t be able to parse it. If your “X-Mailgun-Recipient-Variables” header exceeds 998 characters, you should use folding to spread the variables over multiple lines.
They can also supplied through a special construct, called a variables container.
To contain variables you create the following MIME construct:
multipart/mailgun-variables
--application/json (base64 encoded)
--message/rfc822
----original-message
In this construct, JSON will be Base64 encoded and will be stored inside the part body, which will handle recipient variables containing special characters.
Example:
Content-Type: multipart/mailgun-variables; boundary="8686cc907910484e9d21c54776cd791c"
Mime-Version: 1.0
From: bob@bob-mg
Date: Thu, 26 Jul 2012 15:43:07 +0000
Message-Id: <20120726154307.29852.44460@definebox.com>
Sender: bob=bob-mg@definebox.com
--8686cc907910484e9d21c54776cd791c
Mime-Version: 1.0
Content-Type: application/json
Content-Transfer-Encoding: base64
eyJkZXNjcmlwdGlvbiI6ICJrbGl6aGVudGFzIn0=
--8686cc907910484e9d21c54776cd791c
Content-Type: message/rfc822
Mime-Version: 1.0
Date: Thu, 26 Jul 2012 19:42:55 +0400
To: %recipient.description% <support@mailgunhq.com>
From: bob@bob-mg
Subject: (rackspace) Hello
MSK 2012 support@mailgunhq.com %recipient.description%
Message-Id: <20120726154302.29322.40670@definebox.com>
support@mailgunhq.com %recipient.description%
--8686cc907910484e9d21c54776cd791c--
Mailing Lists¶
Mailing Lists provide a convenient way to send to multiple recipients by using an alias email address. Mailgun sends a copy of the message sent to the alias address to each subscribed member of the Mailing List. You can create and maintain your subscriber lists using the API or Control Panel. In addition, you can use Template Variables to create a unique message for each member of the Mailing List.
Overview
To use Mailing Lists you create a Mailing List address, like devs@example.com
and add
member addresses to it. Each time you send a message to devs@example.com
, a copy
of it is delivered to each subscribed member.
Managing a list
You can create Mailing Lists using the Mailing Lists
tab in the Control Panel or through the API.
We support a couple of formats to make your life easier:
you can upload a CSV file with members, use JSON or use form-like file upload.
Creating a mailing list through the API:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/lists \
-F address='LIST@YOUR_DOMAIN_NAME' \
-F description='Mailgun developers list'
import com.mailgun.api.v3.MailgunMailingListApi;
import com.mailgun.enums.AccessLevel;
import com.mailgun.enums.ReplyPreference;
import com.mailgun.model.mailing.lists.MailingListRequest;
import com.mailgun.model.mailing.lists.MailingListResponse;
// ...
public MailingListResponse createMailingList() {
MailgunMailingListApi mailgunMailingListApi = MailgunClient.config(API_KEY)
.createApi(MailgunMailingListApi.class);
MailingListRequest mailingListRequest = MailingListRequest.builder()
.address( "LIST@YOUR_DOMAIN_NAME")
.name("LIST_NAME")
.description("LIST_DESCRIPTION")
.accessLevel(AccessLevel.EVERYONE)
.replyPreference(ReplyPreference.LIST)
.build();
return mailgunMailingListApi.createMailingList(mailingListRequest);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$mailing_list = 'LIST@YOUR_DOMAIN_NAME';
$list_name = 'Mailgun Subscribers';
$list_description = 'News and service updates';
$access_level = 'readonly';
# Issue the call to the client.
$result = $mgClient->mailingList()->create($mailing_list, $list_name, $list_description, $access_level);
def create_mailing_list():
return requests.post(
"https://api.mailgun.net/v3/lists",
auth=('api', 'YOUR_API_KEY'),
data={'address': 'LIST@YOUR_DOMAIN_NAME',
'description': "Mailgun developers list"})
def create_mailing_list
RestClient.post("https://api:YOUR_API_KEY" \
"@api.mailgun.net/v3/lists",
:address => 'LIST@YOUR_DOMAIN_NAME',
:description => "Mailgun developers list")
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class CreateMailingListChunk
{
public static void Main (string[] args)
{
Console.WriteLine (CreateMailingList ().Content.ToString ());
}
public static IRestResponse CreateMailingList ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.Resource = "lists";
request.AddParameter ("address", "LIST@YOUR_DOMAIN_NAME");
request.AddParameter ("description", "Mailgun developers list");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func CreateMailingList(domain, apiKey string) (mailgun.MailingList, error) {
mg := mailgun.NewMailgun(domain, apiKey)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
return mg.CreateMailingList(ctx, mailgun.MailingList{
Address: "list@example.com",
Name: "dev",
Description: "Mailgun developers list.",
AccessLevel: mailgun.AccessLevelMembers,
})
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
(async () => {
try {
const newList = await client.lists.create({
address: "list_name@${DOMAIN}",
name: "list_name",
description: "list_description",
access_level: "everyone", // readonly (default), members, everyone
});
console.log('newList', newList);
} catch (error) {
console.error(error);
}
})();
Adding a member through the API:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/lists/LIST@YOUR_DOMAIN_NAME/members \
-F subscribed=True \
-F address='bar@example.com' \
-F name='Bob Bar' \
-F description='Developer' \
-F vars='{"age": 26}'
import com.mailgun.api.v3.MailgunMailingListApi;
import com.mailgun.model.mailing.lists.MailingListMemberResponse;
import com.mailgun.model.mailing.lists.MailingListNewMemberRequest;
import java.util.Map;
// ...
public MailingListMemberResponse addListMember() {
MailgunMailingListApi mailgunMailingListApi = MailgunClient.config(API_KEY)
.createApi(MailgunMailingListApi.class);
Map<String, Object> vars = vars = Map.of("age", "26");
MailingListNewMemberRequest request = MailingListNewMemberRequest.builder()
.address("bob@example.com")
.name("Bob Bar")
.vars(vars)
.subscribed(true)
.build();
return mailgunMailingListApi.addMemberToMailingList(MAILING_LIST_ADDRESS, request);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$mailing_list = 'LIST@YOUR_DOMAIN_NAME';
$address ='bob@example.com';
$name = 'Bob';
$vars = array("id" => "123456");
# Issue the call to the client.
$result = $mgClient->mailingList()->member()->create(
$mailing_list,
$address,
$name,
$vars);
def add_list_member():
return requests.post(
"https://api.mailgun.net/v3/lists/LIST@YOUR_DOMAIN_NAME/members",
auth=('api', 'YOUR_API_KEY'),
data={'subscribed': True,
'address': 'bar@example.com',
'name': 'Bob Bar',
'description': 'Developer',
'vars': '{"age": 26}'})
def add_list_member
RestClient.post("https://api:YOUR_API_KEY" \
"@api.mailgun.net/v3/lists/LIST@YOUR_DOMAIN_NAME/members",
:subscribed => true,
:address => 'bar@example.com',
:name => 'Bob Bar',
:description => 'Developer',
:vars => '{"age": 26}')
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class AddListMemberChunk
{
public static void Main (string[] args)
{
Console.WriteLine (AddListMember ().Content.ToString ());
}
public static IRestResponse AddListMember ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.Resource = "lists/{list}/members";
request.AddParameter ("list", "LIST@YOUR_DOMAIN_NAME",
ParameterType.UrlSegment);
request.AddParameter ("address", "bar@example.com");
request.AddParameter ("subscribed", true);
request.AddParameter ("name", "Bob Bar");
request.AddParameter ("description", "Developer");
request.AddParameter ("vars", "{\"age\": 26}");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func AddListMember(domain, apiKey string) error {
mg := mailgun.NewMailgun(domain, apiKey)
memberJoe := mailgun.Member{
Address: "joe@example.com",
Name: "Joe Example",
Subscribed: mailgun.Subscribed,
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
return mg.CreateMember(ctx, true, "mailingList@example.com", memberJoe)
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
(async () => {
try {
const newMember = await client.lists.members.createMember(DOMAIN,
{
address: 'bob@example.com',
name: 'Bob Barr',
vars: JSON.stringify({age: 27}),
subscribed: 'yes',
upsert: 'yes'
}
);
console.log('newMember', newMember);
} catch (error) {
console.error(error);
}
})();
Note
You can attach a JSON dictionary with the structured data to each member
of the mailing list and reference that data in the message body using Template Variables (see vars
parameter in the example above).
Note
There are two modes available when adding a new member: strict and upsert. Strict will raise an error in case the member already exists, upsert will update an existing member if it’s here or insert a new one. Learn how to toggle between the the modes and skip malformed addresses in the Mailing Lists API section.
Sending to a list
You can set the access level of Mailing Lists to:
- Only allow the administrator to post to the list (limited to an API call or authenticated SMTP session);
- Allow Mailing List members to post to the list; or
- Allow anybody to post to the list.
Replying to a list
You can set the preferred method for where a reply to the list should go:
list
Replies to the list go to the list address. This is the default setting for any new list created, except for read-only lists, where replies can only go to the sender. Reply-all will still go to the list.sender
Replies to the list go to the sender (FROM) address. This is the default and only option for read-only lists.
Template Variables
There are some pre-defined variables you can use to
personalize your message to each recipient. When adding members to a Mailing List you can also define
your own variables in addition to these pre-defined variables by using the vars
parameter.
Variable | Description |
---|---|
%recipient% | Full recipient spec, like “Bob <bob@example.com>” (for using as value for “To” MIME header). |
%recipient_email% | Recipient’s email address, like bob@example.com. |
%recipient_name% | Recipient’s full name, like “John Q. Public”. |
%recipient_fname% | Recipient’s first name. |
%recipient_lname% | Recipient’s last name. |
%unsubscribe_url% | A generated URL which allows users to unsubscribe from messages. |
%mailing_list_unsubscribe_url% | A generated URL which allows users to unsubscribe from mailing lists. |
%unsubscribe_email% | An email address which can be used for automatic unsubscription by adding it to List-Unsubscribe MIME header. |
%recipient.yourvar% | Accessing a custom datavalue. (see Attaching Data to Messages) |
Unsubscribing
For managing unsubscribes in Mailing Lists, you can use %mailing_list_unsubscribe_url%
.
We will generate the unique link to unsubscribe from the mailing list.
Once a recipient clicks on the unsubscribe link, we mark the recipient as
“unsubscribed” from this list and they won’t get any further emails addressed
to this list. Note, that you can still override the “unsubscribe” setting via
the API or the Control Panel (in case of user error or accidental unsubscribe,
for example). You can also manually unsubscribe the customer without using any
links via the API or in the Control Panel. Read more in the Mailing Lists
API section.
Mailing Lists and Routes
Mailing Lists work independently from Routes. If there is a Mailing List or Route with the same address, the incoming message will hit the Route and Mailing List simultaneously. This can be pretty convenient for processing replies to the Mailing List and integrating into things like forums or commenting systems.
Templates¶
Mailgun allows you to store predefined templates via Template API and use them to send messages via sending api just providing template name. Don’t be confused with Template Variables as Templating works independently.
Mailgun’s templates uses a fork of the very popular template engine handlebars. To provide values for substitution you have to use Attaching Data to Messages. Let’s see how to send a message using the template feature:
First create a template via the Template API
curl -s --user 'api:YOUR_API_KEY' -X POST \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/templates \
-F template='<div class="entry"> <h1>{{title}}</h1> <div class="body"> {{body}} </div> </div>' \
-F name = 'template.test'
-F description='Sample template'
import com.mailgun.api.v3.MailgunTemplatesApi;
import com.mailgun.model.templates.TemplateRequest;
import com.mailgun.model.templates.TemplateWithMessageResponse;
// ...
public TemplateWithMessageResponse createTemplate() {
MailgunTemplatesApi mailgunTemplatesApi = MailgunClient.config(API_KEY)
.createApi(MailgunTemplatesApi.class);
TemplateRequest request = TemplateRequest.builder()
.template("<div class=\"entry\"> <h1>{{title}}</h1> <div class=\"body\"> {{body}} </div> </div>")
.name("template.name")
.description("Sample template")
.build();
return mailgunTemplatesApi.storeNewTemplate(YOUR_DOMAIN_NAME, request);
}
# Currently, the PHP SDK does not support the Templates endpoint.
# Consider using the following php curl function.
function add_template() {
$params = array(
'template' => '<div class="entry"> <h1>{{title}}</h1> <div class="body"> {{body}} </div> </div>',
'name' => 'Test template',
'description' => 'sample_template'
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, 'api:PRIVATE_API_KEY');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_URL, 'https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/templates');
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
def add_template():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/templates",
auth=("api", "YOUR_API_KEY"),
data={'template': '<div class="entry"> <h1>{{title}}</h1> <div class="body"> {{body}} </div> </div>',
'name': 'Test template',
'description': 'Sample template'})
def add_template
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/templates",
:template => '<div class="entry"> <h1>{{title}}</h1> <div class="body"> {{body}} </div> </div>',
:name => 'Test template',
:description: => 'Sample template'
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class CreateTemplatesChunk
{
public static void Main (string[] args)
{
Console.WriteLine (CreateTemplate ().Content.ToString ());
}
public static IRestResponse CreateTemplate ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.Resource = "{domain}/templates";
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.AddParameter ("template", "<div class=\"entry\"> <h1>{{title}}</h1> <div class=\"body\"> {{body}} </div> </div>");
request.AddParameter ("description", "Sample template");
reuqest.AddParameter ("name", "Test template");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func SendMessageWithTemplate(domain, apiKey string) error {
mg := mailgun.NewMailgun(domain, apiKey)
var err error
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
// Create a new template
err = mg.CreateTemplate(ctx, &mailgun.Template{
Name: "my-template",
Version: mailgun.TemplateVersion{
Template: `'<div class="entry"> <h1>{{.title}}</h1> <div class="body"> {{.body}} </div> </div>'`,
Engine: mailgun.TemplateEngineGo,
Tag: "v1",
},
})
if err != nil {
return err
}
// Give time for template to show up in the system.
time.Sleep(time.Second * 1)
// Create a new message with template
m := mg.NewMessage("Excited User <excited@example.com>", "Template example", "")
m.SetTemplate("my-template")
// Add recipients
m.AddRecipient("bob@example.com")
m.AddRecipient("alice@example.com")
// Add the variables to be used by the template
m.AddVariable("title", "Hello Templates")
m.AddVariable("body", "Body of the message")
_, id, err := mg.Send(ctx, m)
return err
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
(async () => {
try {
const createdTemplate = await client.domains.domainTemplates.create(DOMAIN, {
name: 'template.test',
description: 'Sample template',
template: "<div class=\"entry\"> <h1>{{title}}</h1> <div class=\"body\"> {{body}} </div> </div>",
tag: 'v1',
comment: 'comment text'
});
console.log('createdTemplate', createdTemplate);
} catch (error) {
console.error(error);
}
})();
Response returns stored template information
{
"template": {
"createdAt": "Wed, 29 Aug 2018 23:31:13 UTC",
"description": "Sample template",
"name": "template.test",
},
"message": "template has been stored"
}
You can now use the template when sending a message:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Sender Bob <sbob@YOUR_DOMAIN_NAME>' \
-F to='alice@example.com' \
-F subject='Hello' \
-F template='template.test' \
-F t:variables='{"title": "API documentation", "body": "Sending messages with templates"}'
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import com.mailgun.model.message.MessageResponse;
import java.util.Map;
// ...
public MessageResponse sendMessageByTemplateId() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
Map<String, String> mailgunVariables = Map.of(
"title", "API Documentation",
"body", "Sending messages with templates"
);
Message message = Message.builder()
.from("Excited User <YOU@YOUR_DOMAIN_NAME>")
.to("alice@example.com")
.subject("Hello")
.template(TEMPLATE_NAME)
.tracking(false)
.mailgunVariables(mailgunVariables)
.build();
return mailgunMessagesApi.sendMessage(YOUR_DOMAIN_NAME, message);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>',
'to' => 'bob@example.com',
'subject' => 'Hello',
'template' => 'template.test',
't:variables' => '{"title": "API Documentation", "body": "Sending messages with templates"}'
);
# Make the call to the client.
$result = $mgClient->messages()->send($domain, $params);
def send_message_by_template_id():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
data={"from": "Excited User <YOU@YOUR_DOMAIN_NAME>",
"to": ["bar@example.com", "baz@example.com"],
"subject": "Hello",
"template": "template.test",
"t:variables": json.dumps({"title": "API documentation", "body": "Sending messages with templates"}))
def send_message_by_template_id
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
:from => "Excited User <YOU@YOUR_DOMAIN_NAME>",
:to => "bar@example.com, baz@example.com",
:subject => "Hello",
:template => "template.test",
:"t:variables" => '{"title": "API Documentation", "body": "Sending messages with template"}'
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendMessageByTemplateIdChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendMessageByTemplateId ().Content.ToString ());
}
public static IRestResponse SendMessageByTemplateId ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter ("from", "Excited User <YOU@YOUR_DOMAIN_NAME>");
request.AddParameter ("to", "bar@example.com");
request.AddParameter ("to", "baz@example.com");
request.AddParameter ("subject", "Hello");
request.AddParameter ("template", "template.test");
request.AddParameter ("t:variables", "{\"title\": \"API Documentation\", \"body\": \"Sending messages with templates\"}");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"encoding/json"
"github.com/mailgun/mailgun-go"
"time"
)
func SendMessageWithTemplate() (id string , err error) {
mg := mailgun.NewMailgun("YOUR_DOMAIN_NAME", "YOUR_API_KEY")
ctx, cancel := context.WithTimeout(context.Background(), time.Second * 30)
defer cancel()
m := mg.NewMessage("Excited User <YOU@YOUR_DOMAIN_NAME>", "???", "")
m.SetTemplate("template.test")
if err := m.AddRecipient("bar@example.com"); err != nil {
return "", err
}
vars, err := json.Marshal(map[string]string{
"title": "API Documentation",
"body": "Sending messages with templates",
})
if err != nil {
return "", err
}
m.AddHeader("X-Mailgun-Template-Variables", string(vars))
_, id, err := mg.Send(ctx, m)
return
}
const API_KEY = 'YOUR_API_KEY';
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: API_KEY });
const title = 'title value';
const slug = 'slug value';
const data = {
from: 'Excited User <me@samples.mailgun.org>',
to: 'alice@example.com',
subject: `Email ${title}`,
template: 'name-of-the-template-you-made-in-mailgun-web-portal',
't:variables': JSON.stringify({ // be sure to stringify your payload
title,
slug,
})
};
client.messages.create(DOMAIN, data).then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
If you are sending a MIME you can instead pass template variables via the X-Mailgun-Template-Variables
header.
Note
It is possible to use values defined via v:
option or X-Mailgun-Variables
in your templates.
However if you do so, the variables are included in the delivered message via the X-Mailgun-Variables
header. If this is not desired, use the t:variables
option or X-Mailgun-Template-Variables
header instead.
Handlebars
Speaking of Handlebars, one of the cool things you can do with Handelbars is use their block helpers, which are easy ways to implement dynamic content in your template. Our implementation of Handlebars supports the following helpers: if, unless, each, with, and equal. Let’s explore what each of these do and some quick examples:
The if
block helper
The if
block helper will allow you to conditionally render a block in your template. For example, if you wanted to use a template that would dynamically change language body, you would include the following in your HTML:
{{#if english}}
<p>This text is in the English language.</p>
{{else if spanish}}
<p>Este texto está en idioma español.</p>
{{else if french}}
<p>Ce texte est en langue française.</p>
{{/if}}
In order to send the spanish version, for example, you would pass the h:X-Mailgun-Variables
parameter with the following JSON data: {“spanish” : “true”}
The unless
block helper
The unless
helper is essentially the inverse of the if
helper. The block will only be rendered if the expression returns a false value. Include the following in your HTML:
{{#unless paid}}
<h3 class="warning">WARNING: Your account is past due and will be suspended shortly. Please contact our billing department for assistance</h3>
{{/unless}}
An example JSON payload would look like this: {“paid” : “false”}
The each
block helper
Using the each
helper, you can iterate through a list. Include the following in your HTML:
{{#each user.services}}
<li>You scheduled {{this.service}} on {{this.date}}</li>
{{/each}}
Your JSON data could look something like this:
{
"user":
{
"services":
[
{
"date":"07/30/2019",
"service":"deliverability consultation"
},
{
"date":"08/05/2019",
"service":"sales consultation"
}
]
}
}
The email would end up looking like this:
- You scheduled deliverability consultation on 07/30/2019
- You scheduled sales consultation on 08/05/2019
The equal
helper
The equal
helper renders a block if the string version of both arguments are equals. For example, if you include the following in your HTML:
<p>{{#equal foo "bar"}}foo is bar{{/equal}}</p>
<p>{{#equal foo baz}}foo is the same as baz{{/equal}}</p>
<p>{{#equal nb 0}}nothing{{/equal}}</p>
<p>{{#equal nb 1}}there is one{{/equal}}</p>
<p>{{#equal nb "1"}}everything is stringified before comparison{{/equal}}</p>
And pass the h:X-Mailgun-Variables
parameter with the following JSON data: {“foo”: “bar”, “baz”: “bar”, “nb”: 1}
The resulting email would end up looking like this:
foo is bar
foo is the same as baz
there is one
everything is stringified before comparison
Scheduling Delivery¶
Mailgun also allows you to request a specific time for your message delivery by
using the o:deliverytime
parameter if sending via the API, or
X-Mailgun-Deliver-By
MIME header if sending via SMTP.
While messages are not guaranteed to arrive at exactly the requested time due to the dynamic nature of the queue, Mailgun will do its best.
Note
If your billing plan supports 7 or more days of storage capability, you can now schedule emails out up to 7 days.
Scheduling Delivery API Example
Supply RFC 2822#section-3.3 or Unix epoch time to schedule your message. Time should be in GMT/UTC or timezone should be in numerical offset format:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Sender Bob <sbob@YOUR_DOMAIN_NAME>' \
-F to='alice@example.com' \
-F subject='Hello' \
-F text='Testing some Mailgun awesomness!' \
-F o:deliverytime='Fri, 14 Oct 2011 23:10:10 -0000'
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import com.mailgun.model.message.MessageResponse;
import java.time.ZonedDateTime;
// ...
public MessageResponse sendScheduledMessage() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
Message message = Message.builder()
.from("Excited User <USER@YOURDOMAIN.COM>")
.to("bruce@example.com")
.subject("Hello")
.text("Testing out some Mailgun awesomeness!")
.deliveryTime(ZonedDateTime.now().plusHours(2L)) // Two hours delay.
.build();
return mailgunMessagesApi.sendMessage(YOUR_DOMAIN_NAME, message);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>',
'to' => 'bob@example.com',
'subject' => 'Hello',
'text' => 'Testing some Mailgun awesomness!',
'o:deliverytime' => 'Wed, 01 Jan 2020 09:00:00 -0000'
);
# Make the call to the client.
$result = $mgClient->messages()->send($domain, $params);
def send_scheduled_message():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
data={"from": "Excited User <YOU@YOUR_DOMAIN_NAME>",
"to": "bar@example.com",
"subject": "Hello",
"text": "Testing some Mailgun awesomness!",
"o:deliverytime": "Fri, 25 Oct 2011 23:10:10 -0000"})
def send_scheduled_message
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
:from => "Excited User <YOU@YOUR_DOMAIN_NAME>",
:to => "bar@example.com",
:subject => "Hello",
:text => "Testing some Mailgun awesomeness!",
"o:deliverytime" => "Fri, 25 Oct 2011 23:10:10 -0000"
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendScheduledMessageChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendScheduledMessage ().Content.ToString ());
}
public static IRestResponse SendScheduledMessage ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter ("from", "Excited User <YOU@YOUR_DOMAIN_NAME>");
request.AddParameter ("to", "bar@example.com");
request.AddParameter ("subject", "Hello");
request.AddParameter ("text", "Testing some Mailgun awesomness!");
request.AddParameter ("o:deliverytime",
"Fri, 14 Oct 2011 23:10:10 -0000");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func SendScheduledMessage(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mg.NewMessage(
"Excited User <YOU@YOUR_DOMAIN_NAME>",
"Hello",
"Testing some Mailgun awesomeness!",
"bar@example.com",
)
m.SetDeliveryTime(time.Now().Add(5 * time.Minute))
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}
const API_KEY = 'YOUR_API_KEY';
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({username: 'api', key: API_KEY});
const messageData = {
from: 'Excited User <me@samples.mailgun.org>',
to: 'alice@example.com',
subject: 'Hello',
text: 'Testing some Mailgun awesomeness!',
"o:deliverytime": 'Fri, 6 Jul 2017 18:10:10 -0000'
};
client.messages.create(YOUR_DOMAIN_NAME, messageData)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
Sending in Test Mode¶
You can send messages in test mode by setting o:testmode
parameter to true
.
When you do this, Mailgun will accept the message but will not send it.
This is useful for testing purposes.
Note
You are charged for messages sent in test mode.
Tracking Messages¶
Once you start sending and receiving messages, it’s important to track what’s happening with them. We try to make tracking your messages as easy as possible through Events, Stats, and Tagging.
In addition, Mailgun permanently stores when a message can not be delivered due to a hard bounce (permanent failure) or when a recipient unsubscribes or complains of spam. In these cases, Mailgun will not attempt to deliver to these recipients in the future, in order to protect your sending reputation.
Mailgun provides a variety of methods to access data on your emails:
- View and search Events through the
Logs
tab in the Control Panel to see every event that has happened to every message. You can search by fields like recipient, subject line and even fields that don’t show up in the Logs, like message-id. Data is stored for at least 30 days for paid accounts and at least 2 days for free accounts. - Access data on Events programmatically through the Events API. Data is stored for at least 30 days for paid accounts and at least 2 days for free accounts.
- View, search and edit tables for Bounces, Unsubscribes and Spam Complaints in the
Suppressions
tab of the Control Panel or their respective APIs (Bounces API, Unsubscribes API, Complaints API). Data is stored indefinitely. - Access statistics aggregated by tags in the
Analytics
tab of the Control Panel or the Stats API. Data is stored for at least 6 months. - Receive notifications of events through a Webhook each time an Event happens and store the data on your side.
Enable Tracking
Event tracking is automatically enabled except for Unsubscribes, Opens and Clicks.
You can enable Unsubscribes tracking for your domain via the Domains
tab of the Control Panel.
You can also manage unsubscribes per message by using unsubscribe variables (see Tracking Unsubscribes)
You can enable Opens & Clicks tracking on two levels: per sending domain and per message.
- You can enable Open & Click tracking on a per domain basis under the Tracking Settings section of a particular domain’s settings page.
- Tracking can also be toggled by setting
o:tracking
,o:tracking-clicks
ando:tracking-opens
parameters when sending your message. This will override the domain-level setting.
Note
You will also have to point CNAME records to mailgun.org for Mailgun to rewrite links and track opens. In addition, there needs to be an html part of message for Mailgun to track opens (see Tracking Opens and Tracking Clicks for more detail).
You can also disable click tracking for a specific link by including the html attribute disable-tracking=true
in the html tag of the link.
e.g. <a href="http://mailgun.com" disable-tracking=true>Mailgun</a>
With this html attribute in the link’s html tag,
Mailgun will not rewrite the url.
Events¶
Mailgun keeps track of every event that happens to every message (both inbound and outbound) and stores this data for at least 30 days for paid accounts and 2 days for free accounts.
Below is the table of events that Mailgun tracks.
Event | Description |
---|---|
accepted | Mailgun accepted the request to send/forward the email and the message has been placed in queue. |
rejected | Mailgun rejected the request to send/forward the email. |
delivered | Mailgun sent the email and it was accepted by the recipient email server. |
failed | Mailgun could not deliver the email to the recipient email server. |
opened | The email recipient opened the email and enabled image viewing. Open tracking must be enabled in the Mailgun control panel, and the CNAME record must be pointing to mailgun.org. |
clicked | The email recipient clicked on a link in the email. Click tracking must be enabled in the Mailgun control panel, and the CNAME record must be pointing to mailgun.org. |
unsubscribed | The email recipient clicked on the unsubscribe link. Unsubscribe tracking must be enabled in the Mailgun control panel. |
complained | The email recipient clicked on the spam complaint button within their email client. Feedback loops enable the notification to be received by Mailgun. |
stored | Mailgun has stored an incoming message |
list_member_uploaded | This event occurs after successfully adding a member to a mailing list. |
list_member_upload_error | This event occurs if an error occurs adding a member to a mailing list. |
list_uploaded | This event occurs after successfully uploading a large list of members to a mailing list. |
You can access Events through a few interfaces:
- Webhooks (we POST data to your URL).
- The Events API (you GET data through the API).
- The
Logs
tab of the Control Panel (GUI).
Events API
You can programmatically query and download events through the Events API.
curl -s --user 'api:YOUR_API_KEY' -G \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/events \
--data-urlencode begin='Fri, 3 May 2013 09:00:00 -0000' \
--data-urlencode ascending=yes \
--data-urlencode limit=25 \
--data-urlencode pretty=yes \
--data-urlencode recipient=joe@example.com
import com.mailgun.api.v3.MailgunEventsApi;
import com.mailgun.model.events.EventsQueryOptions;
import com.mailgun.model.events.EventsResponse;
import java.time.ZoneId;
import java.time.ZonedDateTime;
// ...
public EventsResponse getEvents() {
MailgunEventsApi mailgunEventsApi = MailgunClient.config(API_KEY)
.createApi(MailgunEventsApi.class);
EventsQueryOptions eventsQueryOptions = EventsQueryOptions.builder()
.begin(ZonedDateTime.now().minusDays(5))
.ascending(true)
.limit(1)
.build();
return mailgunEventsApi.getEvents(YOUR_DOMAIN_NAME, eventsQueryOptions);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = 'YOUR_DOMAIN_NAME';
$queryString = array(
'begin' => 'Wed, 1 Jan 2020 09:00:00 -0000',
'ascending' => 'yes',
'limit' => 25,
'pretty' => 'yes',
'recipient' => 'bob@example.com'
);
# Issue the call to the client.
$result = $mgClient->events()->get($domain, $queryString);
def get_logs():
return requests.get(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/events",
auth=("api", "YOUR_API_KEY"),
params={"begin" : "Fri, 3 May 2013 09:00:00 -0000",
"ascending" : "yes",
"limit" : 25,
"pretty" : "yes",
"recipient" : "joe@example.com"})
def get_logs
RestClient.get "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/events",
:params => {
:'begin' => 'Fri, 3 May 2013 09:00:00 -0000',
:'ascending' => 'yes',
:'limit' => 25,
:'pretty' => 'yes',
:'recipient' => 'joe@example.com'
}
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class EventsDateTimeRecipientChunk
{
public static void Main (string[] args)
{
Console.WriteLine (EventsDateTimeRecipient ().Content.ToString ());
}
public static IRestResponse EventsDateTimeRecipient ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/events";
request.AddParameter ("begin", "Fri, 3 May 2013 09:00:00 -0000");
request.AddParameter ("ascending", "yes");
request.AddParameter ("limit", 25);
request.AddParameter ("pretty", "yes");
request.AddParameter ("recipient", "joe@example.com");
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func PrintEventLog(domain, apiKey string) error {
mg := mailgun.NewMailgun(domain, apiKey)
// Create an iterator
it := mg.ListEvents(&mailgun.ListEventOptions{
Begin: time.Now().Add(-50 * time.Minute),
Limit: 100,
Filter: map[string]string{
"recipient": "joe@example.com",
},
})
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
// Iterate through all the pages of events
var page []mailgun.Event
for it.Next(ctx, &page) {
for _, event := range page {
fmt.Printf("%+v\n", event)
}
}
// Did iteration end because of an error?
if it.Err() != nil {
return it.Err()
}
return nil
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
(async () => {
try {
const date = new Date(2021, 10, 1, 0, 0, 0, 0); // Mon Nov 01 2021 00:00:00 GMT+0200
const events = await client.events.get(DOMAIN, {
begin: date.toGMTString(), // Sun, 31 Oct 2021 22:00:00 GMT
ascending: 'yes',
limit: 5
});
console.log('events', events)
} catch (error) {
console.error(error);
}
})();
Sample response:
{
"items": [
{
"tags": [],
"timestamp": 1376325780.160809,
"envelope": {
"sender": "me@samples.mailgun.org",
"transport": ""
},
"event": "accepted",
"campaigns": [],
"user-variables": {},
"flags": {
"is-authenticated": true,
"is-test-mode": false
},
"message": {
"headers": {
"to": "user@example.com",
"message-id": "20130812164300.28108.52546@samples.mailgun.org",
"from": "Excited User <me@samples.mailgun.org>",
"subject": "Hello"
},
"attachments": [],
"recipients": [
"user@example.com"
],
"size": 69
},
"recipient": "user@example.com",
"method": "http"
}
],
"paging": {
"next":
"https://api.mailgun.net/v3/samples.mailgun.org/events/W3siY...",
"previous":
"https://api.mailgun.net/v3/samples.mailgun.org/events/Lkawm..."
}
}
Webhooks¶
Mailgun can make an HTTP/HTTPS POST to your URLs when events occur with your messages. If you would like Mailgun to POST event notifications, you need to provide a callback URL in the Webhooks
tab of the Control Panel. Webhooks are at the domain level so you can provide a unique URL for each domain by using the domain drop down selector. Note, if you want to include an HTTPS endpoint it must be confiured with a trusted CA signed SSL certificate, not a self signed certificate.
You can read more about the data that is posted in the appropriate section below (Tracking Opens, Tracking Clicks, Tracking Unsubscribes, Tracking Spam Complaints, Tracking Failures, Tracking Deliveries). We recommend using http://bin.mailgun.net/ for creating temporary URLs to test and debug your webhooks.
For Webhook POSTs, Mailgun listens for the following codes from your server and reacts accordingly:
- If Mailgun receives a
200 (Success)
code it will determine the webhook POST is successful and not retry. - If Mailgun receives a
406 (Not Acceptable)
code, Mailgun will determine the POST is rejected and not retry. - For any other code, Mailgun will retry POSTing according to the schedule below for Webhooks other than the delivery notification.
If your application is unable to process the webhook request but you do not return a 406 error code, Mailgun will retry (other than for delivery notification) during 8 hours at the following intervals before stop trying: 10 minutes, 10 minutes, 15 minutes, 30 minutes, 1 hour, 2 hour and 4 hours.
The Webhooks API endpoint allows you to programmatically manipulate the webhook URLs defined for a specific domain. Head over to the Webhooks API endpoint documentation.
Payload
When something has happened to your email, your URL will be called with application/json payload and with the following data:
{
“signature”:
{
"timestamp": "1529006854",
"token": "a8ce0edb2dd8301dee6c2405235584e45aa91d1e9f979f3de0",
"signature": "d2271d12299f6592d9d44cd9d250f0704e4674c30d79d07c47a66f95ce71cf55"
}
“event-data”:
{
"event": "opened",
"timestamp": 1529006854.329574,
"id": "DACSsAdVSeGpLid7TN03WA",
// ...
}
}
The ‘signature’ parameters are described in securing webhooks and the ‘event-data’ parameters are the same as described in Event Structure
Securing Webhooks
To ensure the authenticity of event requests, Mailgun signs them and posts the signature along with other webhook parameters:
Parameter | Type | Description |
---|---|---|
timestamp | int | Number of seconds passed since January 1, 1970. |
token | string | Randomly generated string with length 50. |
signature | string | String with hexadecimal digits generate by HMAC algorithm. |
To verify the webhook is originating from Mailgun you need to:
- Concatenate timestamp and token values.
- Encode the resulting string with the HMAC algorithm (using your Webhook Signing Key as a key and SHA256 digest mode).
- Compare the resulting hexdigest to the signature.
- Optionally, you can cache the token value locally and not honor any subsequent request with the same token. This will prevent replay attacks.
- Optionally, you can check if the timestamp is not too far from the current time.
Note
Due to potentially large size of posted data, Mailgun computes an authentication signature based on a limited set of HTTP headers.
Below is a Python (version 3.x.x) code sample used to verify the signature:
import hashlib, hmac
def verify(signing_key, token, timestamp, signature):
hmac_digest = hmac.new(key=signing_key.encode(),
msg=('{}{}'.format(timestamp, token)).encode(),
digestmod=hashlib.sha256).hexdigest()
return hmac.compare_digest(str(signature), str(hmac_digest))
And here’s a sample in Ruby:
require 'openssl'
def verify(signing_key, token, timestamp, signature)
digest = OpenSSL::Digest::SHA256.new
data = [timestamp, token].join
signature == OpenSSL::HMAC.hexdigest(digest, signing_key, data)
end
And here’s a sample in PHP:
function verify($signingKey, $token, $timestamp, $signature)
{
// check if the timestamp is fresh
if (\abs(\time() - $timestamp) > 15) {
return false;
}
// returns true if signature is valid
return \hash_equals(\hash_hmac('sha256', $timestamp . $token, $signingKey), $signature);
}
And here’s a sample in Go
import (
"github.com/mailgun/mailgun-go/v3"
)
func VerifyWebhookSignature(domain, signingKey, timestamp, token, signature string) (bool, error) {
mg := mailgun.NewMailgun(domain, signingKey)
return mg.VerifyWebhookSignature(mailgun.Signature{
TimeStamp: timestamp,
Token: token,
Signature: signature,
})
}
And here’s a sample in Node.js
import crypto from 'node:crypto';
const verify = ({ signingKey, timestamp, token, signature }) => {
const encodedToken = crypto
.createHmac('sha256', signingKey)
.update(timestamp.concat(token))
.digest('hex')
return (encodedToken === signature)
}
Attaching Data to Messages¶
When sending you can attach data to your messages for later retrieval, you can for instance attach campaign identifiers or recipient identifiers to messages to help relate webhook payloads or events retrieved from mailgun back to marketing campaigns or individual recipients in your system.
There are two methods of attaching data to emails. If you are sending email via SMTP, you can attach data by
providing a X-Mailgun-Variables
header. The header data must be in JSON map format. For example:
X-Mailgun-Variables: {"first_name": "John", "last_name": "Smith"}
X-Mailgun-Variables: {"my_message_id": 123}
Multiple X-Mailgun-Variables
headers may be provided and their map values will be combined.
Note
The value of the “X-Mailgun-Variables” header must be valid JSON string, otherwise Mailgun won’t be able to parse it. If your X-Mailgun-Variables header exceeds 998 characters, you should use folding to spread the variables over multiple lines.
If you are sending email via the HTTP API, you can attach data by providing a form parameter prefixed with v:
.
For example:
v:first_name=John
v:last_name=Smith
v:my_message_id=123
The data provided will be included the recipients email via a header called X-Mailgun-Variables
.
Additionally the data will also be available via webhook payloads and events returned from the events API. The
data will be attached to these payloads via the user-variables
field as a JSON map. For Example:
{
"event": "delivered",
"user-variables": {
"first_name": "John",
"last_name": "Smith",
"my_message_id": "123"
}
}
When sending batches of emails, you can use values from recipient variables to provide a custom variable per
recipient using templating. For example given a variable of v:recipient-id=%recipient.id%
and a recipient
variable of {"user1@example.com" : { "id": 123 }}
events and webhooks associated with the recipient
user1@example.com
will contain a user-variable
field with the content of { "recipient-id": "123" }
When using variables, the X-Mailgun-Variables
header will be included in the MIME of the delivered email. This
means that recipients who receive emails when variables are used WILL be able to see the variables if they view
the MIME headers.
Tagging¶
Sometimes it’s helpful to categorize your outgoing email traffic based on some criteria, perhaps separate signup emails from password recovery emails or from user comments. Mailgun lets you tag each outgoing message with a custom value. When you access stats on your messages, they will be aggregated by these tags.
The application of tags is more useful in tandem with our tracking features. Tags are unique to each send and can be used to collect data on different message distributions being sent out (e.g. how many recipients opened messages of a given tag or clicks on linked URLs in messages of a tag). This provides the ability to review the overall performance of tags as well as gives the ability to compare one tag against another. For example, two messages of similar content can be assigned different tags for analysis of which message type had better engagement, more recipient activity, etc.
Note
By default, each account is allowed a maximum of 4,000 tags. Any tags created after the 4,000 tag limit are dropped. If more tags are needed, please contact our support team by creating a support ticket here.
Tagging Code Samples
Supply one or more o:tag
parameters to tag the message.
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Sender Bob <sbob@YOUR_DOMAIN_NAME>' \
-F to='alice@example.com' \
-F subject='Hello' \
-F text='Testing some Mailgun awesomness!' \
-F o:tag='September newsletter' \
-F o:tag='newsletters'
import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import com.mailgun.model.message.MessageResponse;
// ...
public MessageResponse sendTaggedMessage() {
MailgunMessagesApi mailgunMessagesApi = MailgunClient.config(API_KEY)
.createApi(MailgunMessagesApi.class);
Message message = Message.builder()
.from("Excited User <USER@YOURDOMAIN.COM>")
.to("bruce@example.com")
.subject("Hello")
.text("Testing out some Mailgun awesomeness!")
.tag("newsletters")
.tag("September newsletter")
.build();
return mailgunMessagesApi.sendMessage(YOUR_DOMAIN_NAME, message);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
$params = array(
'from' => 'Excited User <YOU@YOUR_DOMAIN_NAME>',
'to' => 'bob@example.com',
'subject' => 'Hello',
'text' => 'Testing some Mailgun awesomness!',
'o:tag' => array('Tag1', 'Tag2', 'Tag3')
);
# Make the call to the client.
$result = $mgClient->messages()->send($domain, $params);
def send_tagged_message():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
data={"from": "Excited User <YOU@YOUR_DOMAIN_NAME>",
"to": "bar@example.com",
"subject": "Hello",
"text": "Testing some Mailgun awesomness!",
"o:tag": ["September newsletter", "newsletters"]})
def send_tagged_message
data = {}
data[:from] = "Excited User <YOU@YOUR_DOMAIN_NAME>"
data[:to] = "bar@example.com"
data[:subject] = "Hello"
data[:text] = "Testing some Mailgun awesomness!"
data["o:tag"] = []
data["o:tag"] << "September newsletter"
data["o:tag"] << "newsletters"
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages", data
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class SendTaggedMessageChunk
{
public static void Main (string[] args)
{
Console.WriteLine (SendTaggedMessage ().Content.ToString ());
}
public static IRestResponse SendTaggedMessage ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter ("from", "Excited User <YOU@YOUR_DOMAIN_NAME>");
request.AddParameter ("to", "bar@example.com");
request.AddParameter ("subject", "Hello");
request.AddParameter ("text", "Testing some Mailgun awesomness!");
request.AddParameter ("o:tag", "September newsletter");
request.AddParameter ("o:tag", "newsletters");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func SendTaggedMessage(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mg.NewMessage(
"Excited User <YOU@YOUR_DOMAIN_NAME>",
"Hello",
"Testing some Mailgun awesomeness!",
"bar@example.com",
)
err := m.AddTag("FooTag", "BarTag", "BlortTag")
if err != nil {
return "", err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}
const API_KEY = 'YOUR_API_KEY';
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({username: 'api', key: API_KEY});
const messageData = {
from: 'Excited User <me@samples.mailgun.org>',
to: 'alice@example',
subject: 'Tagged',
text: 'Testing some Mailgun awesomeness!',
"o:tag" : ['newsletters', 'September newsletter']
};
client.messages.create(YOUR_DOMAIN_NAME, messageData)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
Note
A single message may be marked with up to 3 tags.
Note
Tags are case insensitive and should be ascii only. Maximum tag length is 128 characters.
Tracking Accepted¶
Mailgun can keep track of every time your message send request is accepted and placed in our queue for processing.
You can see when accepted events happen in the Logs
tab. In addition, you can be notified through a webhook when a message is accepted or get the data programmatically through the Events API.
Accepted Webhook
You can specify a webhook URL programmatically using the Webhooks API
to be notified every time a message is accepted for processing by Mailgun.
When Mailgun acceptes the message for processing, we will POST the following webhooks payload to your accepted
URLs
Tracking Opens¶
Mailgun can keep track of every time a recipient opens your messages. You can see when Opens happen in the Logs
tab or see counters of opens aggregated by tags in the Analytics
tab of the Control Panel. In addition, you can be notified through a webhook or get the data programmatically through the Events API.
You can enable Open tracking in the Tracking Settings section of your domain’s settings page in the Domains
tab of your Control Panel or by using the o:tracking
or o:tracking-opens
parameters when sending a message. You will also have to add the appropriate CNAME records to your DNS as specified in the Domain Verification & DNS section, which is also located in your domain’s settings page in the Domains
tab of your Control Panel.
Opens are tracked by including a transparent .png file, which will only work if there is an HTML component to the email (i.e., text only emails will not track opens). You should note that many email service providers disable images by default, so this data will only show up if the recipient clicks on display images button in his/her email.
By default, Mailgun places the open tracking pixel after your HTML content. You can elect to move the pixel to the top of your email via the Tracking Settings section of you domain’s settings page in the Domains
tab, or using the o:tracking-pixel-location-top
parameter when sending a message. If you send large emails, you may be experiencing email message clipping at various inbox providers (over 102KB in file size at Gmail, for example) that can impact tracking of open events. Placing the tracking pixel at the top of your HTML content can mitigate these impacts to open rates.
Opens Webhook
You can specify webhook URLs programmatically using the Webhooks API.
When a user opens one of your emails, your opened
URLs will be called with the following webhooks payload.
Tracking Clicks¶
Mailgun can keep track of every time a recipient clicks on links in your messages. You can see when clicks happen in the Logs
tab or see counters of clicks aggregated by tags in the Analytics
tab of the Control Panel. In addition, you can be notified through a webhook or get the data programmatically through the Events API.
You can enable click tracking in the Tracking Settings section of your domain’s settings page in the Domains
tab of your Control Panel or by using the o:tracking
or o:tracking-clicks
parameters when sending a message. You will also have to add the appropriate CNAME records to your DNS as specified in the Domain Verification & DNS section, which is also located in your domain’s settings page in the Domains
tab of your Control Panel. If you enable Click tracking, links will be overwritten and pointed to our servers so we can track clicks.
Clicks Webhook
You can specify webhook URLs programmatically using the Webhooks API.
Every time a user clicks on a link inside of your messages, your clicked
URLs will be called with the following webhooks payload.
Open and Click Bot Detection¶
Mailgun uses tracking pixels and URL redirects to track when a user opens the message and clicks links in the email. However, there are various third-party automated systems that will automatically open a message and follow links for virus scanning and user activity obfuscation, such as Apple Mail Privacy Protection
These automated systems can affect the accuracy of open and click tracking, as a result mailgun attempts to detect
when one of these systems retrieves the tracking pixel or clicks a link. When we detect a bot opening or clicking
a link in the email we will indicate this via the client-info.bot
field in the open/click events.
{
"client-info": {
"client-name": "unknown",
"client-type": "unknown",
"user-agent": "Mozilla/5.0",
"device-type": "unknown",
"client-os": "unknown",
"bot": "apple"
},
"tags": [],
"timestamp": 1652883435.279025,
"recipient": "bot@apple.com",
"geolocation": {
"region": "Unknown",
"country": "US",
"city": "Unknown"
},
"event": "opened",
}
the bot
field can have one of the following possible values
Value Description apple Indicates an Apple MPP bot gmail Indicates a Gmail bot generic Indicates an unknown bot (most likely a firewall or anti virus scan) (empty) If the bot
field is empty, no bot was detected.
Tracking Unsubscribes¶
Mailgun can keep track of every time a recipient requests to be unsubscribed from your mailings. If you enable unsubscribe tracking, Mailgun will insert unsubscribe links and remove those recipients from your mailings automatically for you.
You can see when unsubscribes happen in the Logs
tab or see counters of unsubscribes aggregated by tags in the Analytics
tab of the Control Panel. In addition, you can be notified through a webhook or get the data programmatically through the Events API or the Bounces API.
Mailgun supports three types of unsubscribes: domain, tag or Mailing Lists levels.
- Domain level: Once recipient selects to unsubscribe from domain, he will not receive any more messages from this sending domain.
- Tag level: Sometimes you need to separate traffic by types, for example provide newsletter mailings, security updates mailings and so on.
Recipients may want to unsubscribe from your newsletters but still receive security updates.
For this purpose you can use tags: mark your messages by setting approriate
X-Mailgun-Tag
header and use special%tag_unsubscribe_url%
variable (see below). - Mailing Lists level: If a recipient unsubscribes from a Mailing List, they will still be a member of the Mailing List but will be flagged as unsubscribed and Mailgun will no longer send messages from that Mailing List to the unsubscribed recipient.
Auto-Handling
You can enable Mailgun’s Unsubscribe functionality by turning it on in the settings area for your domain. We will automatically prevent future emails being sent to recipients that have unsubscribed. You can edit the unsubscribed address list from your Control Panel or through the API.
Note
Before enabling, you will need to configure the required DNS entries provided in your Control Panel.
Mailgun provides you with several unsubscribe variables:
Variable | Description |
---|---|
%unsubscribe_url% | link to unsubscribe recipient from all messages sent by given domain |
%tag_unsubscribe_url% | link to unsubscribe from all tags provided in the message |
%mailing_list_unsubscribe_url% | link to unsubscribe from future messages sent to a mailing list |
If you include these variables in your emails, any recipient that clicks on the url will be automatically unsubscribed and those email addresses will be blocked from receiving future emails from that domain or message tag as appropriate.
Mailgun can automatically provide an unsubscribe footer in each email you send. You can customize your unsubscribe footer by editing the settings in the Control Panel.
To enable/disable unsubscribes programmatically per message you can do the following:
- Enable unsubscription feature for your domain.
- Remove text in the html and text footer templates so they won’t be appended automatically.
- Insert a variable in the html and text bodies of your email when you need unsubscribe links.
- This variable will be replaced by the corresponding unsubscribe link.
In the Suppressions
tab of the Control Panel or through the API you can also:
- View/get a list of unsubscribed addresses.
- Remove an unsubscribed address from the list.
- Add a new unsubscribed address.
Take a look at Unsubscribes section of the API reference to learn how to programmatically manage lists of unsubscribed users.
Unsubscribes Webhook
You can specify webhook URLs programmatically using the Webhooks API.
When a user unsubscribes, Mailgun will invoke unsubscribed
webhook with the following webhooks payload.
Tracking Spam Complaints¶
Mailgun automatically keeps track of every time a recipient complains that a message is spam.
You can see when complaints happen in the Logs
tab or see counters of complaints, aggregated by tags, in the Analytics
tab of the Control Panel. In addition, you can be notified through a webhook or get the data programmatically through the Events API or the Complaints API.
Email service providers (“ESPs”) are very sensitive to users clicking on spam complaint buttons and it’s important to monitor that activity to maintain a good sending reputation. While, not every ESP supports Feedback Loop (“FBL”) notifications, we make sure that you get data on all of the ones that do. We will remove recipients from future messages if a complaint has been filed by that recipient. This is necessary to maintain your reputation and not have your emails automatically sent to spam folders.
Spam Complaint tracking is always enabled.
Mailgun provides Spam complaints API to programmatically manage the lists of users who have complained.
Spam Complains Webhook
You can specify webhook URLs programmatically using the Webhooks API.
When a user reports one of your emails as spam, Mailgun will invoke complained
webhook with the following webhooks payload.
Tracking Failures¶
Mailgun tracks all delivery failures. Failures consist of both Hard Bounces (permanent failures) and Soft Bounces (temporary failures).
An email message is said to “bounce” if it is rejected by the recipient SMTP server.
With respect to failure persistence Mailgun classifies bounces into the following two groups:
- Hard bounces (permanent failure): Recipient is not found and the recipient
email server specifies the recipient does not exist. Mailgun stops attempting
delivery to invalid recipients after one Hard Bounce. These addresses are
added to the “Bounces” table in the
Suppressions
tab of your Control Panel and Mailgun will not attempt delivery in the future. - Soft bounces (temporary failure): Email is not delivered because the mailbox
is full or for other reasons. These addresses are not added to the “Bounces” table in
the
Suppressions
tab.
With respect to when the recipient SMTP server rejected an incoming message Mailgun classifies bounces into the following two groups:
- Immediate bounce: An email message is rejected by the recipient SMTP server during the SMTP session.
- Delayed (asynchronous) bounce: The recipient SMTP server accepts an email message during the SMTP session. After some time it will then send a Non-Delivery Report email message to the message sender.
Note
In the case of a bounce Mailgun will retry to deliver the message only if the bounce was both Immediate and Soft. After several unsuccessful attempts Mailgun will quit retrying in order to maintain your sending reputation.
Warning
Mailgun can track delayed bounces but only if the domain, that the email message was sent from, has MX records pointing to Mailgun. Otherwise NDR email messages won’t reach Mailgun. Please refer to Verifying Your Domain for details on how to do that.
You can see when bounces happen in the Logs
tab or see counters of
bounces, aggregated by tags, in the Analytics
tab of the Control Panel. In addition, you can be
notified through a webhook or get the data programmatically through the
Events API or the Bounces API.
Mailgun provides Bounces API to programmatically manage the lists of hard bounces.
Permanent Failure Webhook
There are a few reasons why Mailgun needs to stop attempting to deliver messages and drop them.
The most common reason is that Mailgun received a Hard bounce or repeatedly received Soft bounces
and continuing attempting to deliver may hurt your reputation with the receiving ESP.
Also, if the address is on one of the ‘do not send lists’ because that recipient
had previously bounced, unsubscribed, or complained of spam, we will not attempt delivery and drop the message.
If one of these events occur we will POST the following webhooks payload to your permanent_fail
URLs.
You can specify webhook URLs programmatically using the Webhooks API.
Temporary Failure Webhook
If Mailgun got a Soft bounce (temporary failure) we will POST the following webhooks payload to your temporary_fail
URLs.
You can specify webhooks URLs programmatically using the Webhooks API.
Tracking Deliveries¶
Mailgun tracks all successful deliveries of messages. A successful delivery occurs when the recipient email server responds that it has accepted the message.
You can see when deliveries happen in the Logs
tab. In addition, you can be notified through a webhook when a message is delivered or get the data programmatically through the Events API.
Delivered Event Webhook
You can specify a webhook URL programmatically using the Webhooks API
to be notified every time a message is delivered.
If the message is successfully delivered to the intended recipient,
we will POST the following webhooks payload to your delivered
URLs
Secure Tracking¶
Mailgun supports enabling the HTTPS protocol on open, click and unsubscribe tracking URLs with the :ref:`Secure Tracking API <secure-tracking>. Mailgun utilizes Let’s Encrypt with HTTP-01 challenges via your existing tracking CNAME record to issue a TLS certificate. This configuration also supports HTTP Strict Transport Security (HSTS).
Note
One-click HTTPS tracking links are available on all subscription plans. The Flex plan, which is a pay-as-you-grow usage-based plan, does not include this feature. If your account is using the Flex plan, please see the CDN Alternative help article.
Tracking CNAME
You must first ensure that you have a tracking CNAME in place pointing to mailgun.org or eu.mailgun.org if your domain is in our EU infrastructure. By default, the DNS record is “email.<domain.com>”, however, the tracking hostname is configurable via the Mailgun application, so just be sure the hostname in your CNAME record matches the tracking hostname set.
Receiving, Forwarding and Storing Messages¶
Mailgun allows you to receive emails through Routes. Routes will accept emails and then perform an action which can include:
- Forwarding the email to a different email address.
- POSTing the data in the email to a URL.
- Storing the email temporarily for subsequent retrieval through a GET request.
Routes¶
You can define a list of routes to handle incoming emails. This idea of routes is borrowed from MVC web frameworks like Django or Ruby on Rails: if a message matches a route expression, Mailgun can forward it to your application via HTTP or to another email address or store the message temporarily (3 days) for subsequent retrieval.
You can define routes visually in the Control Panel, or programmatically using the Routes API.
A Route is a pair of filter+action. Each incoming message is passed to a filter expression, and if it evaluates to true, the action is executed.
Each Route can be assigned a priority. Routes are evaluated in the order of priority, with lower numbers having a higher priority. The default is for all Routes to be evaluated (even if a higher priority Route is triggered). To avoid this you can use a stop()
action (see below).
Here’s a more formal list of route properties:
Field | Description |
---|---|
Priority | Integer indicating the priority of route execution. Lower numbers have higher priority. |
Filter | Filters available in routes - match_recipient() match_header() catchall() (see below for description). |
Actions | Type of action to take when a filter is triggered - forward() store() stop() (see below for description). |
Description | Arbitrary string to describe the route (shown in the Control Panel UI) |
Note
The length of the Filter or Action fields cannot exceed 4k. If you need more actions or filters than is allowed under the 4k limit, you can add additional routes. Multiple routes with the same Filter expression are allowed. This will allow you to add many more Actions for the same Filter but spread across multiple route entries.
Route Filters¶
Route filters are expressions that determine when an action is triggered. You can create a filter based on the recipient of the incoming email, the headers in the incoming email or use a catch-all filter. Filters support regular expressions in the pattern to give you a lot of flexibility when creating them.
match_recipient(pattern)
Matches the SMTP recipient of the incoming message against the regular expression pattern. For example this filter will match messages going to foo@bar.com:
match_recipient("foo@bar.com")
You can use Python-style regular expression in your filter. For example, this will match all messages coming to any recipient at @bar.com:
match_recipient(".*@bar.com")
Another example, handling plus addressing for a specific recipient:
match_recipient("^chris\+(.*)@example.com$")
Mailgun supports regexp captures in filters. This allows you to use captured values
inside of your actions. The example below captures the local name (the part of email before @)
and passes it as a mailbox
parameter to an application URL:
route filter : match_recipient("(.*)@bar.com")
route action : forward("http://myhost.com/post/?mailbox=\1")
You can use named captures as well (Note: capture groups cannot be used in the hostname):
route filter : match_recipient("(?P<user>.*?)@(?P<domain>.*)")
route action : forward("http://mycallback.com/domains/\g<domain>/users/\g<user>")
match_header(header, pattern)
Similar to match_recipient
but instead of looking at a message recipient, it applies
the pattern to an arbitrary MIME header of the message.
The example below matches any message with a word “support” in its subject:
match_header("subject", ".*support")
The example below matches any message against several keywords:
match_header('subject', '(.*)(urgent|help|asap)(.*)')
The example below will match any messages deemed spam (if spam filtering is enabled):
match_header('X-Mailgun-Sflag', 'Yes')
match_recipient(pattern) AND match_header(header, pattern)
The example below will match any recipient for a domain, then match if the message is in English:
match_recipient('^(.*)@example.com$') and match_header("Content-Language", "^(.*)en-US(.*)$")
catch_all()
Matches if no preceeding routes matched. Usually you need to use it in a route with a lowest priority, to make sure it evaluates last.
Route Actions¶
If a route expression evaluates to true, Mailgun executes the corresponding action.
Currently you can use the following three actions in your routes: forward()
, store()
and stop()
.
forward(destination)
Forwards the message to a specified destination, which can be another email address or a URL. A few examples:
forward("mailbox@myapp.com")
forward("http://myapp.com/messages")
You can combine multiple destinations separating them by a comma:
forward("http://myapp.com/messages, mailbox@myapp.com")
Note
If you forward messages to another email address, then you should disable click tracking, open tracking and unsubscribes, by editing your domain settings in the Control Panel. If these features are enabled, the content of each message is modified by Mailgun before forwarding, which invalidates the DKIM signature. If the message comes from a domain publishing a DMARC policy (like Yahoo! Mail), the message will be rejected as spam by the forwarding destination.
store(notification endpoint)
Stores the message temporarily (for up to 3 days) on Mailgun’s servers so that you can retrieve it later. This is helpful for large attachments that may cause time-outs or if you just want to retrieve them later to reduce the frequency of hits on your server.
You can specify a URL and we will notify you when the email arrives along with a URL where you can use to retrieve the message:
store(notify="http://mydomain.com/callback")
If you don’t specify a URL with the notify parameter, the message will still be stored and you can get the message later through the Messages API. You can see a full list of parameters we will post/return to you below.
stop()
Simply stops the priority waterfall so the subsequent routes will not be evaluated. Without a stop() action executed, all lower priority Routes will also be evaluated.
Receiving Messages via HTTP through a forward() action¶
When you specify a URL of your application as a route destination through a forward() action, Mailgun will perform an HTTP POST request into it using one of two following formats:
- Fully parsed: Mailgun will parse the message, transcode it into UTF-8 encoding, process attachments, and attempt to separate quoted parts from the actual message. This is the preferred option.
- Raw MIME: message is posted as-is. In this case you are responsible for parsing MIME.
To receive raw MIME messages, the destination URL must end with
mime
.
For Route POSTs, Mailgun listens for the following codes from your server and reacts accordingly:
- If Mailgun receives a
200 (Success)
code it will determine the webhook POST is successful and not retry. - If Mailgun receives a
406 (Not Acceptable)
code, Mailgun will determine the POST is rejected and not retry. - For any other code, Mailgun will retry POSTing according to the schedule below for Webhooks other than the delivery notification.
If your application is unable to process the webhook request but you do not return a 406 error code, Mailgun will retry (other than for delivery notification) during 8 hours at the following intervals before stop trying: 10 minutes, 10 minutes, 15 minutes, 30 minutes, 1 hour, 2 hour and 4 hours.
Below are two tables of HTTP parameters that you can expect to be posted into your application through a forward() action.
Note
In addition to these parameters Mailgun will post all MIME headers.
Note
Do not rely on the body-plain
, stripped-text
, and stripped-signature
fields for HTML sanitization. These fields
merely provide content from the text/plain portion of an incoming message. This content may contain unescaped HTML.
Parsed Messages Parameters¶
Parameter | Type | Description |
---|---|---|
recipient | string | recipient of the message as reported by MAIL TO during SMTP chat. |
sender | string | sender of the message as reported by MAIL FROM during SMTP chat. Note: this value may differ
from From MIME header. |
from | string | sender of the message as reported by From message header, for example “Bob <bob@example.com>”. |
subject | string | subject string. |
body-plain | string | text version of the email. This field is always present. If the incoming message only has HTML body, Mailgun will create a text representation for you. |
stripped-text | string | text version of the message without quoted parts and signature block (if found). |
stripped-signature | string | the signature block stripped from the plain text message (if found). |
body-html | string | HTML version of the message, if message was multipart. Note that all parts of the message will be posted, not just text/html. For instance if a message arrives with “foo” part it will be posted as “body-foo”. |
stripped-html | string | HTML version of the message, without quoted parts. |
attachment-count | int | how many attachments the message has. |
attachment-x | string | attached file (‘x’ stands for number of the attachment). Attachments are handled as file uploads,
encoded as multipart/form-data . |
timestamp | int | number of seconds passed since January 1, 1970 (see securing webhooks). |
token | string | randomly generated string with length 50 (see securing webhooks). |
signature | string | string with hexadecimal digits generate by HMAC algorithm (see securing webhooks). |
message-headers | string | list of all MIME headers dumped to a json string (order of headers preserved). |
content-id-map | string | JSON-encoded dictionary which maps Content-ID (CID) of each attachment to the corresponding attachment-x parameter. This allows you to map posted attachments to tags like <img src='cid'> in the message body. |
Note the message-headers
parameter. It was added because not all web frameworks support multi-valued keys parameters. For example Ruby on Rails requires a special syntax to post params like that: you need to add [] to a key to collect it’s values on the server side as an array. Below is a Ruby on Rails example of obtaining MIME headers via message-headers
parameter:
def mailgun_posted_params
message_headers = JSON.parse(params["message-headers"])
message_headers.each do |header|
key, value = header
puts "header key: #{key}, header value: #{value}"
end
end
MIME Messages Parameters¶
Parameter | Type | Description |
---|---|---|
recipient | string | recipient of the message. |
sender | string | sender of the message as reported by SMTP MAIL FROM. |
from | string | sender of the message as reported by From message header, for example “Bob <bob@example.com>”. |
subject | string | subject string. |
body-mime | string | full MIME envelope. You will need a MIME parsing library to process this data. |
timestamp | int | number of seconds passed since January 1, 1970 (see securing webhooks). |
token | string | randomly generated string with length 50 (see securing webhooks). |
signature | string | string with hexadecimal digits generate by HMAC algorithm(see securing webhooks). |
Note
To receive raw MIME messages and perform your own parsing you must configure a route with a URL ending with “mime”, like http://myhost/post_mime.
Note
Consider using http://bin.mailgun.net to debug and play with your routes. This tool allows you to forward incoming messages to a temporary URL and inspecting the posted data. No programming required.
Storing and Retrieving Messages¶
When storing an email through a store()
action in a Route, you can chose to be notified when the message is stored by including a URL with the notify parameter when setting up the store action or you can retrieve the message later by searching for the message through the Events API and retrieving it through the Messages API.
If you set a URL to be posted when the message is received (store(notify="http://mydomain.com/callback")
), or retrieve the message later through a GET request to the Messages API, the following parameters are posted/returned in JSON.
Parameter Type Description domain string domain name this message was received for. recipient string recipient of the message as reported by MAIL TO
during SMTP chat.sender string sender of the message as reported by MAIL FROM
during SMTP chat. Note: this value may differ fromFrom
MIME header.from string sender of the message as reported by From
message header, for example “Bob Lee <blee@mailgun.net>”.subject string subject string. body-plain string text version of the email. This field is always present. If the incoming message only has HTML body, Mailgun will create a text representation for you. stripped-text string text version of the message without quoted parts and signature block (if found). stripped-signature string the signature block stripped from the plain text message (if found). body-html string HTML version of the message, if message was multipart. Note that all parts of the message will be posted, not just text/html. For instance if a message arrives with “foo” part it will be posted as “body-foo”. stripped-html string HTML version of the message, without quoted parts. attachments string contains a json list of metadata objects, one for each attachment, see below. message-url string a URL that you can use to get and/or delete the message. Only present in the payload posted to the notification URL. timestamp int number of seconds passed since January 1, 1970 (see securing webhooks). token string randomly generated string with length 50 (see securing webhooks). signature string string with hexadecimal digits generate by HMAC algorithm (see securing webhooks). message-headers string list of all MIME headers dumped to a json string (order of headers preserved). content-id-map string JSON-encoded dictionary which maps Content-ID (CID) of each attachment to the corresponding attachment-x
parameter. This allows you to map posted attachments to tags like<img src='cid'>
in the message body.
The attachments JSON contains the following items.
Parameter | Type | Description |
---|---|---|
size | integer | indicates the size of the attachment in bytes. |
url | string | contains the url where the attachment can be found. This does not support DELETE. |
name | string | the name of the attachment |
content-type | string | the content type of the attachment |
Alternatively, you can choose the following parameters when the Accept
header is set to message/rfc2822
Parameter Type Description recipient string recipient of the message. sender string sender of the message as reported by SMTP MAIL FROM. from string sender of the message as reported by From
message header, for example “Bob <bob@example.com>”.subject string subject string. body-mime string full MIME envelope. You will need a MIME parsing library to process this data.
API Routing Samples¶
You can define routes programmatically using our HTTP API like in these examples.
Create a route of the highest priority with multiple actions:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/routes \
-F priority=0 \
-F description='Sample route' \
-F expression='match_recipient(".*@YOUR_DOMAIN_NAME")' \
-F action='forward("http://myhost.com/messages/")' \
-F action='stop()'
import com.mailgun.api.v3.MailgunRoutesApi;
import com.mailgun.model.routes.RoutesRequest;
import com.mailgun.model.routes.RoutesResponse;
// ...
public RoutesResponse createRoute() {
MailgunRoutesApi mailgunRoutesApi = MailgunClient.config(API_KEY)
.createApi(MailgunRoutesApi.class);
RoutesRequest routesRequest = RoutesRequest.builder()
.priority(0)
.description("sample route")
.expression("match_recipient('.*@YOUR_DOMAIN_NAME')")
.action("forward('http://myhost.com/messages/')")
.action("stop()")
.build();
return mailgunRoutesApi.createRoute(routesRequest);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
# Define your expression, actions, and description
$expression = 'match_recipient(".*@mg.example.com")';
$actions = array('forward("my_address@example.com")', 'stop()');
$description = 'Catch All and Forward';
# Issue the call to the client.
$result = $mgClient->routes()->create($expression, $actions, $description);
def create_route():
return requests.post(
"https://api.mailgun.net/v3/routes",
auth=("api", "YOUR_API_KEY"),
data={"priority": 0,
"description": "Sample route",
"expression": "match_recipient('.*@YOUR_DOMAIN_NAME')",
"action": ["forward('http://myhost.com/messages/')", "stop()"]})
def create_route
data = {}
data[:priority] = 0
data[:description] = "Sample route"
data[:expression] = "match_recipient('.*@YOUR_DOMAIN_NAME')"
data[:action] = []
data[:action] << "forward('http://myhost.com/messages/')"
data[:action] << "stop()"
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/routes", data
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class CreateRouteChunk
{
public static void Main (string[] args)
{
Console.WriteLine (CreateRoute ().Content.ToString ());
}
public static IRestResponse CreateRoute ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.Resource = "routes";
request.AddParameter ("priority", 0);
request.AddParameter ("description", "Sample route");
request.AddParameter ("expression", "match_recipient('.*@YOUR_DOMAIN_NAME')");
request.AddParameter ("action",
"forward('http://myhost.com/messages/')");
request.AddParameter ("action", "stop()");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func CreateRoute(domain, apiKey string) (mailgun.Route, error) {
mg := mailgun.NewMailgun(domain, apiKey)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
return mg.CreateRoute(ctx, mailgun.Route{
Priority: 1,
Description: "Sample Route",
Expression: "match_recipient(\".*@YOUR_DOMAIN_NAME\")",
Actions: []string{
"forward(\"http://example.com/messages/\")",
"stop()",
},
})
}
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
(async () => {
try {
const createdRoute = await client.routes.create({
expression: 'match_recipient(".*@YOUR_DOMAIN_NAME")',
action: ['forward("http://myhost.com/messages/")', 'stop()'],
description: 'Sample route'
});
console.log('createdRoute', createdRoute);
} catch (error) {
console.error(error);
}
})();
Sample response:
{
"message": "Route has been created",
"route": {
"description": "Sample route",
"created_at": "Wed, 15 Feb 2012 13:03:31 GMT",
"actions": [
"forward(\"http://myhost.com/messages/\")",
"stop()"
],
"priority": 0,
"expression": "match_recipient(\".*@samples.mailgun.org\")",
"id": "4f3bad2335335426750048c6"
}
}
Note
Higher priority routes are handled first. Smaller numbers indicate higher priority. Default is 0.
Listing routes:
curl -s --user 'api:YOUR_API_KEY' -G \
https://api.mailgun.net/v3/routes \
-d skip=1 \
-d limit=1
import com.mailgun.api.v3.MailgunRoutesApi;
import com.mailgun.model.routes.RoutesListResponse;
import com.mailgun.model.routes.RoutesPageRequest;
// ...
public RoutesListResponse getRoutes() {
MailgunRoutesApi mailgunRoutesApi = MailgunClient.config(API_KEY)
.createApi(MailgunRoutesApi.class);
RoutesPageRequest pageRequest = RoutesPageRequest.builder()
.skip(0)
.limit(5)
.build();
return mailgunRoutesApi.getRoutesList(pageRequest);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
# Issue the call to the client.
$result = $mgClient->routes()->index();
def get_routes():
return requests.get(
"https://api.mailgun.net/v3/routes",
auth=("api", "YOUR_API_KEY"),
params={"skip": 1,
"limit": 1})
def get_routes
RestClient.get "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/routes", :params => {
:skip => 1,
:limit => 1
}
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class GetRoutesChunk
{
public static void Main (string[] args)
{
Console.WriteLine (GetRoutes ().Content.ToString ());
}
public static IRestResponse GetRoutes ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.Resource = "routes";
request.AddParameter ("skip", 1);
request.AddParameter ("limit", 1);
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func ListRoutes(domain, apiKey string) ([]mailgun.Route, error) {
mg := mailgun.NewMailgun(domain, apiKey)
it := mg.ListRoutes(nil)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
var page, result []mailgun.Route
for it.Next(ctx, &page) {
result = append(result, page...)
}
if it.Err() != nil {
return nil, it.Err()
}
return result, nil
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
(async () => {
try {
const list = await client.routes.list({"skip": 0, "limit": 5});
console.log('list', list);
} catch (error) {
console.error(error);
}
})();
Sample response:
{
"total_count": 266,
"items": [
{
"description": "Sample route",
"created_at": "Wed, 15 Feb 2012 12:58:12 GMT",
"actions": [
"forward(\"http://myhost.com/messages/\")",
"stop()"
],
"priority": 0,
"expression": "match_recipient(\".*@samples.mailgun.org\")",
"id": "4f3babe4ba8a481c6400476a"
}
]
}
Access the route by id:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/routes/4f3bad2335335426750048c6
import com.mailgun.api.v3.MailgunRoutesApi;
import com.mailgun.model.routes.SingleRouteResponse;
// ...
public SingleRouteResponse getSingleRoute() {
MailgunRoutesApi mailgunRoutesApi = MailgunClient.config(API_KEY)
.createApi(MailgunRoutesApi.class);
return mailgunRoutesApi.getSingleRoute(YOUR_ROUTE_ID);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$route_id = '5d9fde0fd8b861ec16cf2549'
# Issue the call to the client.
$result = $mgClient->routes()->show($route_id);
def get_route():
return requests.get(
"https://api.mailgun.net/v3/routes/4e97c1b2ba8a48567f007fb6",
auth=("api", "YOUR_API_KEY"))
def get_route
RestClient.
get("https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/routes/"\
"4e97c1b2ba8a48567f007fb6"){|response, request, result| response }
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class GetRouteChunk
{
public static void Main (string[] args)
{
Console.WriteLine (GetRoute ().Content.ToString ());
}
public static IRestResponse GetRoute ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.Resource = "routes/{id}";
request.AddUrlSegment ("id", "4e97c1b2ba8a48567f007fb6");
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func GetRoute(domain, apiKey string) (mailgun.Route, error) {
mg := mailgun.NewMailgun(domain, apiKey)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
return mg.GetRoute(ctx, "route_id")
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
(async () => {
try {
const route = await client.routes.get('your_route_id');
console.log('route', route);
} catch (error) {
console.error(error);
}
})();
Sample response:
{
"route": {
"description": "Sample route",
"created_at": "Wed, 15 Feb 2012 13:03:31 GMT",
"actions": [
"forward(\"http://myhost.com/messages/\")",
"stop()"
],
"priority": 0,
"expression": "match_recipient(\".*@samples.mailgun.org\")",
"id": "4f3bad2335335426750048c6"
}
}
Credentials¶
Mailgun gives you the ability to programmatically create SMTP credentials which can be used to send mail. SMTP credentials can be used to relay email, through Mailgun, using the SMTP protocol.
SMTP Credentials API Examples¶
Listing all credentials:
curl -s --user 'api:YOUR_API_KEY' -G \
https://api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
public class MGSample {
// ...
public static JsonNode getCredentials() throws UnirestException {
HttpResponse<JsonNode> request = Unirest.get("https://api.mailgun.net/v3/domains/" + YOUR_DOMAIN_NAME + "/credentials")
.basicAuth("api", API_KEY)
.asJson();
return request.getBody();
}
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = "YOUR_DOMAIN_NAME";
# Issue the call to the client.
$result = $mgClient->domains()->credentials($domain);
def get_credentials():
return requests.get(
"https://api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials",
auth=("api", "YOUR_API_KEY"))
def get_credentials
RestClient.get "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials"
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class GetCredentialsChunk
{
public static void Main (string[] args)
{
Console.WriteLine (GetCredentials ().Content.ToString ());
}
public static IRestResponse GetCredentials ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "domains/{domain}/credentials";
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func ListCredentials(domain, apiKey string) ([]mailgun.Credential, error) {
mg := mailgun.NewMailgun(domain, apiKey)
it := mg.ListCredentials(nil)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
var page, result []mailgun.Credential
for it.Next(ctx, &page) {
result = append(result, page...)
}
if it.Err() != nil {
return nil, it.Err()
}
return result, nil
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
(async () => {
try {
const domainCredentials = await client.domains.domainCredentials.list(DOMAIN);
console.log('domainCredentials', domainCredentials);
} catch (error) {
console.error(error);
}
})();
Sample response:
{
"total_count": 2,
"items": [
{
"size_bytes": 0,
"created_at": "Tue, 27 Sep 2011 20:24:22 GMT",
"mailbox": "user@samples.mailgun.org"
"login": "user@samples.mailgun.org"
},
{
"size_bytes": 0,
"created_at": "Thu, 06 Oct 2011 10:22:36 GMT",
"mailbox": "user@samples.mailgun.org"
"login": "user@samples.mailgun.org"
}
]
}
Creating a new SMTP credential:
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials \
-F login='alice@YOUR_DOMAIN_NAME' \
-F password='supasecret'
import com.mailgun.api.v3.MailgunDomainsApi;
import com.mailgun.model.ResponseWithMessage;
import com.mailgun.model.domains.DomainCredentials;
// ...
public ResponseWithMessage createCredentials() {
MailgunDomainsApi mailgunDomainsApi = MailgunClient.config(API_KEY)
.createApi(MailgunDomainsApi.class);
DomainCredentials domainCredentials = DomainCredentials.builder()
.login("alice@YOUR_DOMAIN_NAME.com")
.password( "super_secret_password")
.build();
return mailgunDomainsApi.createNewCredentials(YOUR_DOMAIN_NAME, domainCredentials);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = 'YOUR_DOMAIN_NAME';
$smtpUser = 'bob';
$smtpPass = 'new_password';
# Issue the call to the client.
$result = $mgClient->domains()->createCredential($domain, $smtpUser, $smtpPass);
def create_credentials():
return requests.post(
"https://api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials",
auth=("api", "YOUR_API_KEY"),
data={"login": "alice@YOUR_DOMAIN_NAME",
"password": "secret"})
def create_credentials
RestClient.post "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials",
:login => "alice@YOUR_DOMAIN_NAME",
:password => "secret"
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class CreateCredentialsChunk
{
public static void Main (string[] args)
{
Console.WriteLine (CreateCredentials ().Content.ToString ());
}
public static IRestResponse CreateCredentials ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "domains/{domain}/credentials";
request.AddParameter ("login", "alice@YOUR_DOMAIN_NAME");
request.AddParameter ("password", "secret");
request.Method = Method.POST;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func CreateCredential(domain, apiKey string) error {
mg := mailgun.NewMailgun(domain, apiKey)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
return mg.CreateCredential(ctx, "alice@example.com", "secret")
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
(async () => {
try {
const createdCredentials = await client.domains.domainCredentials.create(DOMAIN, {
login: 'alice@YOUR_DOMAIN_NAME',
password: 'secret'
});
console.log('createdCredentials', createdCredentials);
} catch (error) {
console.error(error);
}
})();
{
"message": "Created 1 credentials pair(s)"
}
Updating the password for a given credential:
curl -s --user 'api:YOUR_API_KEY' -X PUT \
https://api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials/alice \
-F password='abc123'
import com.mailgun.api.v3.MailgunDomainsApi;
import com.mailgun.model.ResponseWithMessage;
// ...
public ResponseWithMessage updatePassword() {
MailgunDomainsApi mailgunDomainsApi = MailgunClient.config(API_KEY)
.createApi(MailgunDomainsApi.class);
return mailgunDomainsApi.updateCredentials(YOUR_DOMAIN_NAME, YOUR_LOGIN, "super_secret_password");
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = 'YOUR_DOMAIN_NAME';
$smtpUser = 'bob';
$smtpPass = 'new_password';
# Issue the call to the client.
$result = $mgClient->domains()->updateCredential($domain, $smtpUser, $smtpPass);
def change_credential_password():
return requests.put(
"https://api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials/alice",
auth=("api", "YOUR_API_KEY"),
data={"password": "supersecret"})
def change_credential_password
RestClient.put "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials/alice",
:password => "supersecret"
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class ChangePwdCredentialsChunk
{
public static void Main (string[] args)
{
Console.WriteLine (ChangeCredentialPassword ().Content.ToString ());
}
public static IRestResponse ChangeCredentialPassword ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "domains/{domain}/credentials/{username}";
request.AddUrlSegment ("username", "alice");
request.AddParameter ("password", "supersecret");
request.Method = Method.PUT;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func ChangePassword(domain, apiKey string) error {
mg := mailgun.NewMailgun(domain, apiKey)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
return mg.ChangeCredentialPassword(ctx, "alice", "super_secret")
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
(async () => {
try {
const updatedCredentials = await client.domains.domainCredentials.update(DOMAIN, 'LOGIN_FROM_CREDENTIALS', {
password: 'new_password'
});
console.log('updatedCredentials -------->', updatedCredentials);
} catch (error) {
console.error(error);
}
})();
Sample response:
{
"message": "Password changed"
}
Deleting a given credential:
curl -s --user 'api:YOUR_API_KEY' -X DELETE \
https://api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials/alice
import com.mailgun.api.v3.MailgunDomainsApi;
import com.mailgun.model.ResponseWithMessage;
// ...
public ResponseWithMessage deleteCredentials() {
MailgunDomainsApi mailgunDomainsApi = MailgunClient.config(API_KEY)
.createApi(MailgunDomainsApi.class);
return mailgunDomainsApi.deleteCredentials(YOUR_DOMAIN_NAME, YOUR_LOGIN);
}
# Include the Autoloader (see "Libraries" for install instructions)
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = Mailgun::create('PRIVATE_API_KEY', 'https://API_HOSTNAME');
$domain = 'YOUR_DOMAIN_NAME';
$smtpUser = 'bob';
# Issue the call to the client.
$result = $mgClient->domains()->deleteCredential($domain, $smtpUser);
def delete_credentials():
return requests.delete(
"https://api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials/alice",
auth=("api", "YOUR_API_KEY"))
def delete_credentials
RestClient.delete "https://api:YOUR_API_KEY"\
"@api.mailgun.net/v3/domains/YOUR_DOMAIN_NAME/credentials/alice"
end
using System;
using System.IO;
using RestSharp;
using RestSharp.Authenticators;
public class DeleteCredentialsChunk
{
public static void Main (string[] args)
{
Console.WriteLine (DeleteCredentials ().Content.ToString ());
}
public static IRestResponse DeleteCredentials ()
{
RestClient client = new RestClient ();
client.BaseUrl = new Uri ("https://api.mailgun.net/v3");
client.Authenticator =
new HttpBasicAuthenticator ("api",
"YOUR_API_KEY");
RestRequest request = new RestRequest ();
request.AddParameter ("domain", "YOUR_DOMAIN_NAME", ParameterType.UrlSegment);
request.Resource = "domains/{domain}/credentials/{login}";
request.AddUrlSegment ("login", "alice");
request.Method = Method.DELETE;
return client.Execute (request);
}
}
import (
"context"
"github.com/mailgun/mailgun-go/v3"
"time"
)
func DeleteCredential(domain, apiKey string) error {
mg := mailgun.NewMailgun(domain, apiKey)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
return mg.DeleteCredential(ctx, "alice")
}
const DOMAIN = 'YOUR_DOMAIN_NAME';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const mailgun = new Mailgun(formData);
const client = mailgun.client({ username: 'api', key: 'YOUR_API_KEY' || '' });
(async () => {
try {
const deletedDomainCredentials = await client.domains.domainCredentials.destroy(DOMAIN, 'alice@YOUR_DOMAIN_NAME');
console.log('deletedDomainCredentials', deletedDomainCredentials);
} catch (error) {
console.error(error);
}
})();
Sample response:
{
"message": "Credentials have been deleted",
"spec": "alice@samples.mailgun.org"
}
Spam Filter¶
If you are receiving email, you need spam filtering. Mailgun spam filtering is powered
by an army of SpamAssassin machines. Mailgun gives you three ways to configure spam
filtering. You can select the appropriate option in the Control Panel when you click
on a domain name in the Domains
tab.
- Disabled (default)
- Delete spam (spam is removed and you won’t see it)
- Mark spam with MIME headers and you decide what to do with it
If you chose option 3, there are four headers we provide for you: X-Mailgun-Sflag
,
X-Mailgun-Sscore
, X-Mailgun-Dkim-Check-Result
and X-Mailgun-Spf
.
X-Mailgun-Sflag
- Inserted with the value ‘Yes’ if the message was classified as a spam.
X-Mailgun-Sscore
A ‘spamicity’ score that you can use to calibrate your own filter. Inserted for every message checked for a spam. The score ranges from low negative digits (very unlikely to be spam) to 20 and occasionally higher (very likely to be spam).
At the time of writing this, we are filtering spam at a score of around 5.0 but we are constantly calibrating this.
X-Mailgun-Dkim-Check-Result
- If DKIM is used to sign an inbound message, Mailgun will attempt DKIM validation, the results will be stored in this header. Possible values are: ‘Pass’ or ‘Fail’
X-Mailgun-Spf
- Mailgun will perform an SPF validation, and results will be stored in this header. Possible values are: ‘Pass’, ‘Neutral’, ‘Fail’ or ‘SoftFail’.
Email Verification¶
Mailgun’s email verification service is a multi-point check of an email address to ensure it exists, is not a high-risk address, is not disposable and more. Maintaining a list of verified and deliverable email addresses is important in order to reduce the ratio of bounced emails and prevent negative impacts to your reputation as a sender.
Mailgun offers verifications in three forms: performing a single verification, verifying the email addresses of the members of a mailing list, and verifying lists in bulk via CSV.
Mailgun’s revamp of our initial verification service now offers a definitive result of our verification check and a risk assessment of the given address. The result parameter will return one of four values, described in the table below. This is the definitive answer for whether or not a sender should use an address and replaces the is_valid parameter in v3 verifications.
deliverable | An address that has a high likelihood of being legitimate and has passed all verification checks. |
---|---|
undeliverable | An address that is confirmed invalid and should be discarded from a list or corrected. Sending to this address may damage sender reputation. |
do_not_send | An address that may damage sender reputation if messages or sent. |
unknown | Unable to make a decision about the validity of the supplied address, usually due to a timeout when attempting to verify an address. |
The risk parameter will return one of three values, described in the table below. This helps senders understand how sending to an address may impact reputation or the potential impact of allowing a user onto a platform.
low | An address that is likely legitimate and sending has a low likelihood of damaging reputation if the address has been obtained in a legitimate manner - we’ll make this assumption based on well known domains (hotmail.com, gmail.com, etc) |
---|---|
medium | The default or neutral state for risk calculation. An address that isn’t deemed a low or high risk will default to a medium risk. |
high | An address that has a high risk of damaging sender reputation or when used for verification should be challenged for validity. |
Role-based Address Check¶
For all verification requests, we provide whether an address is a role-based address (e.g. postmaster@, info@, etc.). These addresses are typically distribution lists with a much higher complaint rate since unsuspecting users on the list can receive a message they were not expecting.
Disposable Mailbox Detection¶
Disposable mailboxes are commonly used for fraudulent purposes. Mailgun can detect whether the address provided is on a known disposable mailbox provider and given the determination, you may choose how to proceed based on your own risk decisions. It is important to check for disposable mailboxes to ensure communication between user and web application.
List Verification¶
The members of a Mailing List that exists within Mailgun can be verified with the click of a button in the Control Panel as demonstrated below:
Note that the existing limitation of a maximum of 2.5 million members still exists for Mailing Lists.
Bulk Verification¶
A CSV no greater than 25 MB can be submitted via API or Control Panel for verification en masse. In addition, a gzip no greater than 25 MB containing a single CSV of whatever size can also be submitted which greatly increases the potential size of the list that can take.
- Note that the following conditions must be met:
- The column of email addresses must be preceded with a single cell with the text email or email_address in it. Any other columns will be ignored. This text must be all lowercase.
- The size of the CSV must not exceed 25 MB, and the size of the gzip must not exceed 25 MB.
- For best results, do not include a header line in the CSV.
- Make sure the contents of the CSV do not contain any non-ASCII characters. The character set used must be ASCII or UTF-8.
Inbox Placement¶
The Inbox Placement product is an email deliverability tool that provides visibility into where an email will land in the mailbox. While mailbox providers (i.e. Gmail, Yahoo, Hotmail, etc.) will provide feedback that an email is delivered or not (i.e. delivery), they do not provide insight into where in the mailbox the email landed (i.e. deliverability). Specifically, an email can land in the inbox, spam/junk folder, or in the case of Gmail, specific tabs within the inbox. The Inbox Placement product utilizes a mechanism known as seed testing to provide visibility in to where emails are landing.
Seed testing roughly works as follows:
- Mailgun manages a list of seed mailbox accounts with mailbox providers in the market.
- A test email is sent to the seed list.
- Mailgun tracks where the test email landed for each mailbox in the seed list and returns the results to the user. The results contain the following information:
- “Spam”, “Missed”, or “Inbox” placement (and which tab for Gmail mailboxes) for each individual mailbox in our seed list.
- A rollup percentage summary for each mailbox provider. The rollup aggregates the results from each mailbox.
- A rollup percentage summary for each seed test. The rollup aggregates the results from each mailbox provider.
Creating a Test¶
Creating and running an Inbox Placement test is simple and only requires minimal configuration in either the control panel or the API:
Required
- A sending domain to send a test from. If you haven’t set up a domain please see the Verifying Your Domain section.
- A message subject.
- A message body.
Optional
- Provide a ‘from’ address prefix. Mailgun provides ‘user’ by default.
Once configured, a test can be performed. Please wait for results to come in.
Test Results¶
When an Inbox Placement test is performed, we attempt to match (or “group”) emails sent to our various seed mailboxes together to generate a test Result. To do this, we use email criteria such as sender, timeframe, headers, and subject.
To ensure accurate test results and reduce missing email rates, we recommend setting a unique “matching” header per test. See the example below using the Mailgun API:
curl -s --user 'api:API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN/messages \
-F to='<SEEDLIST>' \
-F subject='<SUBJECT>' \
-F html='<HTML>' \
-F h:X-Campaign-Id='51302fd2-8d88-4e90-baa8-c25622f57009'
Below is a list of supported (case insensitive) email headers that will be used to match emails when generating Inbox Placement test results:
- X-IBP-DebugHeader
- X-Mailjet-Campaign
- X-job
- X-cid
- X-NSS
- X-250ok-CID
- X-rpcampaign
- rpcampaign
- X-PVIQ
- X-Campaign-ID
- X-CampaignID
- X-10pl8ID
- X-Campaign_ID
- X-eC-messenger-mid
- X-EMarSys-Identify
- X-jobid
- X-Listid
- X-MailingID
SMTP Protocol¶
In addition to our HTTP API, Mailgun servers supports the standard SMTP protocol… You can send using SMTP with or without TLS.
Please consult a standard library documentation for language of your choice to learn how to use the SMTP protocol. Below are some helpful links for a few popular languages:
SMTP Relay¶
You can also configure your own mailserver to relay mail via Mailgun as shown below. All of them require these three variables which you can look up in the Control Panel:
- Your SMTP username
- Your SMTP password
- SMTP host name mailserver (these instructions will use smtp.mailgun.org as an example)
You have an SMTP username and password for each domain you have at Mailgun. To send mail from a particular domain, just use the appropriate credentials.
Postfix Instructions
You have to configure a relay host with SASL authentication like shown below:
# /etc/postfix/main.cf:
mydestination = localhost.localdomain, localhost
relayhost = [smtp.mailgun.org]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = static:postmaster@mydomain.com:password
smtp_sasl_security_options = noanonymous
# TLS support
smtp_tls_security_level = may
smtpd_tls_security_level = may
smtp_tls_note_starttls_offer = yes
When using TLS encryption, make sure Postfix knows where to locate the CA database for your Linux distribution:
smtpd_tls_key_file = /etc/ssl/private/smtpd.key
smtpd_tls_cert_file = /etc/ssl/certs/smtpd.crt
smtpd_tls_CApath = /etc/ssl/certs
Note
You can use SMTP Credentials, but not your Control Panel password.
Exim Instructions
For more information see Exim’s documentation for authenticated outgoing SMTP. You need to configure “smarthost” for your Exim setup.
# In your exim.conf:
# In routes configuration:
mailgun:
driver = manualroute
domains = ! +local_domains
transport = mailgun_transport
route_list = * smtp.mailgun.org byname
# In transports configuration:
mailgun_transport:
driver=smtp
hosts_require_auth = <; $host_address
hosts_require_tls = <; $host_address
Also make sure to configure login credentials (in your /etc/exim/passwd.client):
*.mailgun.org:username:password
Sendmail Instructions
Define the smarthost in your sendmail.mc before mailer definitions:
## Mailgun
define(`SMART_HOST', `smtp.mailgun.org')dnl
FEATURE(`authinfo', `hash /etc/mail/authinfo')dnl
# optional, see http://www.sendmail.org/m4/features.html before enabling:
# FEATURE(`accept_unresolvable_domains')dnl
# FEATURE(`accept_unqualified_senders')dnl
# execute: make -C /etc/mail
## Mailgun
Specify login credentials in your authinfo:
AuthInfo:smtp.mailgun.org "U:<LOGIN>" "P:<PASSWORD>" "M:PLAIN"
Don’t forget to run the following command and then restart sendmail:
make -C /etc/mail
Using Standard Email Clients¶
Standard email clients like Thunderbird or Outlook can also be used to send mail.
Settings for sending mail:
SMTP server: smtp.mailgun.org
Note
Use a full address like “user@mymailgundomain.com” as a login for SMTP. SSL or TLS are supported.
TLS Sending Connection Settings¶
For message delivery, Mailgun exposes two flags that will work at the domain level or message level (message level will override domain level) that allow you to control how messages are delivered. See documentation for sending messages and domains for examples on how these fields can be updated.
require tls: If set to True this requires the message only be sent over a TLS connection. If a TLS connection can not be established, Mailgun will not deliver the message. If set to False, Mailgun will still try and upgrade the connection, but if Mailgun can not, the message will be delivered over a plaintext SMTP connection. The default is False.
skip verification: If set to True, the certificate and hostname will not be verified when trying to establish a TLS connection and Mailgun will accept any certificate during delivery. If set to False, Mailgun will verify the certificate and hostname. If either one can not be verified, a TLS connection will not be established. The default is False.
To help you better understand the configuration possibilities and potential issues, take a look at the following table. Take into account the type of threat you are concerned with when making your decision on how to configure sending settings. By default, require-tls and skip-verification are false.
require-tls | skip-verification | TLS | TLS Active Attack (MITM) | TLS Passive Attack (Capture) | Passive Plaintext Capture |
---|---|---|---|---|---|
false | false | Attempt | Not Possible | Not Possible | Possible via downgrade |
false | true | Attempt | Possible | Not Possible | If STARTTLS not offered |
true | false | Required | Not Possible | Not Possible | Not Possible |
true | true | Required | Possible | Not Possible | Not Possible |
Additionally the following fields are available in your logs under delivery-status to indicate how the message was delivered:
Field | Description |
---|---|
tls | Indicates if a TLS connection was used or not when delivering the message. |
certificate-verified | Indicates if we verified the certificate or not when delivering the message. |
mx-host | Tells you the MX server we connected to to deliver the message. |
Internationalization¶
Internationalized Domain Names (IDN)
Our messages API supports sending to addresses that leverage internationalized domain names in the to and from fields. When necessary, Mailgun will automatically convert the domains to the ASCII equivalent through the use of punycode
At this time, sending domains cannot be created using non-ASCII characters.
Internationalized Email Addresses (SMTPUTF8)
Mailgun supports internationalized email addresses through the use of the SMTPUTF8 extension. An internationalized email address will contain a non-ASCII character in the local-part portion of the email address and may also use an internationalized domain name.
Mailgun supports internationalized email addresses in the following portions of our product:
- Outgoing Messages (HTTP API / SMTP endpoint)
- Inbound SMTP
- Routes (match_recipient and forward action)
- Mailing Lists (list names and member addresses)
- Email Verification
- Suppressions Lists
In order to send messages to an internationalized email address, the receiving mailbox provider must support the SMTPUTF8 extension.