Search code examples
pythonpython-requestsmultipartform-dataemail-attachmentsmailgun

Send attached file with Mailgun using python


I'm trying to send an email with an attached file with the Mailgun API using requests.post.

In their documentation they alert that you must use multipart/form-data encoding when sending attachments, I'm trying this:

import requests
MAILGUN_URL = 'https://api.mailgun.net/v3/sandbox4f...'
MAILGUN_KEY = 'key-f16f497...'


def mailgun(file_url):
    """Send an email using MailGun"""

    f = open(file_url, 'rb')

    r = requests.post(
        MAILGUN_URL,
        auth=("api", MAILGUN_KEY),
        data={
            "subject": "My subject",
            "from": "[email protected]",
            "to": "[email protected]",
            "text": "The text",
            "html": "The<br>html",
            "attachment": f
        },
        headers={'Content-type': 'multipart/form-data;'},
    )

    f.close()

    return r


mailgun("/tmp/my-file.xlsx")

I've defined the header to be sure that the content type is multipart/form-data, but when I run the code, I get a 400 status with reason: Bad Request

What's wrong? I need be sure that i'm using multipart/form-data and I'm using correctly the attachment parameter


Solution

  • You need to use the files keyword argument. Here is the documentation in requests.

    And an example from the Mailgun documentation:

    def send_complex_message():
        return requests.post(
            "https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
            auth=("api", "YOUR_API_KEY"),
            files=[("attachment", open("files/test.jpg")),
                   ("attachment", open("files/test.txt"))],
            data={"from": "Excited User <YOU@YOUR_DOMAIN_NAME>",
                  "to": "[email protected]",
                  "cc": "[email protected]",
                  "bcc": "[email protected]",
                  "subject": "Hello",
                  "text": "Testing some Mailgun awesomness!",
                  "html": "<html>HTML version of the body</html>"})
    

    So modify your POST to:

    r = requests.post(
        MAILGUN_URL,
        auth=("api", MAILGUN_KEY),
        files = [("attachment", f)],
        data={
            "subject": "My subject",
            "from": "[email protected]",
            "to": "[email protected]",
            "text": "The text",
            "html": "The<br>html"
        },
        headers={'Content-type': 'multipart/form-data;'},
    )
    

    This should work fine for you.


    Quick note about the headers arg:

    Specifying headers in a request is perfectly fine with both requests and Mailgun. In fact, headers are set in the first example in Mailgun's documentation.

    That said, there are some caveats that you should be aware if if you choose to send headers. From requests documentation (emphasis mine):

    Note: Custom headers are given less precedence than more specific sources of information. For instance:

    Authorization headers set with headers= will be overridden if credentials are specified in .netrc, which in turn will be overridden by the auth= parameter. Requests will search for the netrc file at ~/.netrc, ~/_netrc, or at the path specified by the NETRC environment variable.

    Authorization headers will be removed if you get redirected off-host.

    Proxy-Authorization headers will be overridden by proxy credentials provided in the URL.

    Content-Length headers will be overridden when we can determine the length of the content.

    Furthermore, Requests does not change its behavior at all based on which custom headers are specified. The headers are simply passed on into the final request.