Sometimes you want to send mail. You could do it from your server, but it would probably get caught in a spam filter! Instead, we’re going to be using the service Mailgun.

When you sign up with Mailgun, you wind up at a page that tells you a lot of things you need to do. You only need to activate your account.

Once you’re activated, you end up on https://app.mailgun.com/app/dashboard?activation_complete=true. You see a note that says FREE ACCOUNTS ARE RESTRICTED TO AUTHORIZED RECIPIENTS ONLY - you’ll need to add yourself as an authorized recipient, which you can do at https://app.mailgun.com/app/account/authorized. After you try to add yourself, you’ll get another email from Mailgun. Click the link inside to confirm that you want to receive emails from Mailgun.

Now you can start sending mail!

You can read the documentation if you’d like. You’re going to want to use the ‘API’ version (not SMTP), and you have to make sure you’re looking at the Python documentation. You do this by scrolling up to the very top of the page and clicking Python. It’s pretty hidden, it’s at the very top!

Their code looks like this (if you take it out of the function):

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!"}) 

And you say “Wait! I don’t have a domain name!”… but you do have one - Mailgun gave it to you, they just didn’t tell you.

To find it, go to https://app.mailgun.com/app/dashboard and scroll down to your “Sandbox domains.” Sandboxes are where you test things. Click the domain to see the details (like IP, API base URL, password, and API key - you won’t use all of that information)

Fill in the blanks for everything in ALL_CAPS, changing the ‘from’ and ‘to’ both be yourself (in the example it’s actually sending to two email addresses).

If you run the code in a single cell, it will only tell you if it worked (200) or didn’t work (any other number). It’s more helpful if you actually look at the response, like this:

response = 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!"}) 
response.text

If it says Queued, thank you then you’re doing okay! If the response says Domain not found you’ll want to check your domain (and make sure it has /messages after it). If it says Forbidden your API key is probably wrong.

If you’d like to send an email with an attachment, you can adjust your code a little bit:

response = requests.post(
        "https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages"../,
        auth=("api", "YOUR_API_KEY"),
        files=[("attachment", open("test.csv"))],
        data={"from": "Excited User <mailgun@YOUR_DOMAIN_NAME>",
              "to": ["bar@example.com", "YOU@YOUR_DOMAIN_NAME"],
              "subject": "Hello",
              "text": "Testing some Mailgun awesomness!"}) 
response.text

If you’re sending an image, you might want to do open("test.jpg", "rb") instead - rb means “read it as binary data, not as text data.” Binary data is just, well, not text data.

If you want to send multiple attachments, it becomes files=[("attachment", open("test1.csv")), ("attachment", open("test2.csv")), ("attachment", open("test3.csv"))],.