
Research
NPM targeted by malware campaign mimicking familiar library names
Socket uncovered npm malware campaign mimicking popular Node.js libraries and packages from other ecosystems; packages steal data and execute remote code.
Welcome to the official Python SDK for Mailgun!
Check out all the resources and Python code examples in the official Mailgun Documentation.
This library mailgun-python
officially supports the following Python versions:
It's tested up to 3.13 (including).
To build the mailgun-python
package from the sources you need setuptools
(as a build backend), wheel
, and setuptools-scm
.
At runtime the package requires only requests >=2.32.3
.
For running test you need pytest >=7.0.0
at least.
Make sure to provide the environment variables from Authentication.
Use the below code to install the Mailgun SDK for Python:
pip install mailgun-python
Use the below code to install it locally by cloning this repository:
git clone https://github.com/mailgun/mailgun-python
cd mailgun-python
pip install .
Use the below code to install it locally by conda
and make
on Unix platforms:
make install
on Linux or macOS:
git clone https://github.com/mailgun/mailgun-python
cd mailgun-python
make dev
conda activate mailgun
make dev-full
conda activate mailgun-dev
The Mailgun API is part of the Sinch family and enables you to send, track, and receive email effortlessly.
All API calls referenced in our documentation start with a base URL. Mailgun allows the ability to send and receive email in both US and EU regions. Be sure to use the appropriate base URL based on which region you have created for your domain.
It is also important to note that Mailgun uses URI versioning for our API endpoints, and some endpoints may have different versions than others. Please reference the version stated in the URL for each endpoint.
For domains created in our US region the base URL is:
https://api.mailgun.net/
For domains created in our EU region the base URL is:
https://api.eu.mailgun.net/
Your Mailgun account may contain multiple sending domains. To avoid passing the domain name as a query parameter, most API URLs must include the name of the domain you are interested in:
https://api.mailgun.net/v3/mydomain.com
The Mailgun Send API uses your API key for authentication. Grab and save your Mailgun API credentials.
To run tests and examples please use virtualenv or conda environment with next environment variables:
export APIKEY="API_KEY"
export DOMAIN="DOMAIN_NAME"
export MESSAGES_FROM="Name Surname <mailgun@domain_name>"
export MESSAGES_TO="Name Surname <username@gmail.com>"
export MESSAGES_CC="Name Surname <username2@gmail.com>"
export DOMAINS_DEDICATED_IP="127.0.0.1"
export MAILLIST_ADDRESS="everyone@mailgun.domain.com"
export VALIDATION_ADDRESS_1="test1@i.ua"
export VALIDATION_ADDRESS_2="test2@gmail.com"
Initialize your Mailgun client:
from mailgun.client import Client
import os
auth = ("api", os.environ["APIKEY"])
client = Client(auth=auth)
All of Mailgun's HTTP response codes follow standard HTTP definitions. For some additional information and troubleshooting steps, please see below.
400 - Will typically contain a JSON response with a "message" key which contains a human readable message / action to interpret.
403 - Auth error or access denied. Please ensure your API key is correct and that you are part of a group that has access to the desired resource.
404 - Resource not found. NOTE: this one can be temporal as our system is an eventually-consistent system but requires diligence. If a JSON response is missing for a 404 - that's usually a sign that there was a mistake in the API request, such as a non-existing endpoint.
429 - Mailgun does have rate limits in place to protect our system. Please retry these requests as defined in the response. In the unlikely case you encounter them and need them raised, please reach out to our support team.
500 - Internal Error on the Mailgun side. Retries are recommended with exponential or logarithmic retry intervals. If the issue persists, please reach out to our support team.
[!IMPORTANT]
This is a full list of supported endpoints this SDK provides mailgun/examples
Pass the components of the messages such as To, From, Subject, HTML and text parts, attachments, etc. Mailgun will build a MIME representation of the message and send it. Note: In order to send you must provide one of the following parameters: 'text', 'html', 'amp-html' or 'template'
import os
from pathlib import Path
from mailgun.client import Client
key: str = os.environ["APIKEY"]
domain: str = os.environ["DOMAIN"]
client: Client = Client(auth=("api", key))
def post_message() -> None:
# Messages
# POST /<domain>/messages
data = {
"from": os.getenv("MESSAGES_FROM", "test@test.com"),
"to": os.getenv("MESSAGES_TO", "recipient@example.com"),
"subject": "Hello from python!",
"text": "Hello world!",
"o:tag": "Python test",
}
req = client.messages.create(data=data, domain=domain)
print(req.json())
import os
from pathlib import Path
from mailgun.client import Client
key: str = os.environ["APIKEY"]
domain: str = os.environ["DOMAIN"]
client: Client = Client(auth=("api", key))
def post_message() -> None:
# Messages
# POST /<domain>/messages
data = {
"from": os.getenv("MESSAGES_FROM", "test@test.com"),
"to": os.getenv("MESSAGES_TO", "recipient@example.com"),
"subject": "Hello from python!",
"text": "Hello world!",
"o:tag": "Python test",
}
# It is strongly recommended that you open files in binary mode.
# Because the Content-Length header may be provided for you,
# and if it does this value will be set to the number of bytes in the file.
# Errors may occur if you open the file in text mode.
files = [
(
"attachment",
("test1.txt", Path("test1.txt").read_bytes()),
)
]
req = client.messages.create(data=data, files=files, domain=domain)
print(req.json())
def post_scheduled() -> None:
# Scheduled message
data = {
"from": os.environ["MESSAGES_FROM"],
"to": os.environ["MESSAGES_TO"],
"cc": os.environ["MESSAGES_CC"],
"subject": "Hello Vasyl Bodaj",
"html": html,
"o:deliverytime": "Thu Jan 28 2021 14:00:03 EST",
}
req = client.messages.create(data=data, domain=domain)
print(req.json())
import os
from mailgun.client import Client
key: str = os.environ["APIKEY"]
domain: str = os.environ["DOMAIN"]
client: Client = Client(auth=("api", key))
def get_domains() -> None:
"""
GET /domains
:return:
"""
data = client.domainlist.get()
print(data.json())
def get_domains_with_filters() -> None:
"""
GET /domains
:return:
"""
params = {"skip": 0, "limit": 1}
data = client.domainlist.get(filters=params)
print(data.json())
def get_simple_domain() -> None:
"""
GET /domains/<domain>
:return:
"""
domain_name = "python.test.domain4"
data = client.domains.get(domain_name=domain_name)
print(data.json())
def add_domain() -> None:
"""
POST /domains
:return:
"""
# Post domain
data = {
"name": "python.test.domain5",
# "smtp_password": "random123456"
}
request = client.domains.create(data=data)
print(request.json())
print(request.status_code)
def update_simple_domain() -> None:
"""
PUT /domains/<domain>
:return:
"""
domain_name = "python.test.domain5"
data = {"name": domain_name, "spam_action": "disabled"}
request = client.domains.put(data=data, domain=domain_name)
print(request.json())
def get_connections() -> None:
"""
GET /domains/<domain>/connection
:return:
"""
request = client.domains_connection.get(domain=domain)
print(request.json())
def put_dkim_authority() -> None:
"""
PUT /domains/<domain>/dkim_authority
:return:
"""
data = {"self": "false"}
request = client.domains_dkimauthority.put(domain=domain, data=data)
print(request.json())
def get_tracking() -> None:
"""
GET /domains/<domain>/tracking
:return:
"""
request = client.domains_tracking.get(domain=domain)
print(request.json())
import os
from mailgun.client import Client
key: str = os.environ["APIKEY"]
domain: str = os.environ["DOMAIN"]
client: Client = Client(auth=("api", key))
def get_webhooks() -> None:
"""
GET /domains/<domain>/webhooks
:return:
"""
req = client.domains_webhooks.get(domain=domain)
print(req.json())
def create_webhook() -> None:
"""
POST /domains/<domain>/webhooks
:return:
"""
data = {"id": "clicked", "url": ["https://facebook.com"]}
#
req = client.domains_webhooks.create(domain=domain, data=data)
print(req.json())
def put_webhook() -> None:
"""
PUT /domains/<domain>/webhooks/<webhookname>
:return:
"""
data = {"id": "clicked", "url": ["https://facebook.com", "https://google.com"]}
req = client.domains_webhooks_clicked.put(domain=domain, data=data)
print(req.json())
import os
from mailgun.client import Client
key: str = os.environ["APIKEY"]
domain: str = os.environ["DOMAIN"]
client: Client = Client(auth=("api", key))
def get_domain_events() -> None:
"""
GET /<domain>/events
:return:
"""
req = client.events.get(domain=domain)
print(req.json())
def events_by_recipient() -> None:
"""
GET /<domain>/events
:return:
"""
params = {
"begin": "Tue, 24 Nov 2020 09:00:00 -0000",
"ascending": "yes",
"limit": 10,
"pretty": "yes",
"recipient": os.environ["VALIDATION_ADDRESS_1"],
}
req = client.events.get(domain=domain, filters=params)
print(req.json())
import os
from mailgun.client import Client
key: str = os.environ["APIKEY"]
domain: str = os.environ["DOMAIN"]
client: Client = Client(auth=("api", key))
def post_bounces() -> None:
"""
POST /<domain>/bounces
:return:
"""
data = {"address": "test120@gmail.com", "code": 550, "error": "Test error"}
req = client.bounces.create(data=data, domain=domain)
print(req.json())
def get_unsubs() -> None:
"""
GET /<domain>/unsubscribes
:return:
"""
req = client.unsubscribes.get(domain=domain)
print(req.json())
[!IMPORTANT] It is strongly recommended that you open files in binary mode. Because the Content-Length header may be provided for you, and if it does this value will be set to the number of bytes in the file. Errors may occur if you open the file in text mode.
def import_list_unsubs() -> None:
"""
POST /<domain>/unsubscribes/import, Content-Type: multipart/form-data
:return:
"""
files = {
"unsubscribe2_csv": Path(
"mailgun/doc_tests/files/mailgun_unsubscribes.csv"
).read_bytes()
}
req = client.unsubscribes_import.create(domain=domain, files=files)
print(req.json())
def add_complaints() -> None:
"""
POST /<domain>/complaints
:return:
"""
data = {"address": "bob@gmail.com", "tag": "compl_test_tag"}
req = client.complaints.create(data=data, domain=domain)
print(req.json())
[!IMPORTANT] It is strongly recommended that you open files in binary mode. Because the Content-Length header may be provided for you, and if it does this value will be set to the number of bytes in the file. Errors may occur if you open the file in text mode.
def import_complaint_list() -> None:
"""
POST /<domain>/complaints/import, Content-Type: multipart/form-data
:return:
"""
files = {
"complaints_csv": Path(
"mailgun/doc_tests/files/mailgun_complaints.csv"
).read_bytes()
}
req = client.complaints_import.create(domain=domain, files=files)
print(req.json())
def delete_all_whitelists() -> None:
"""
DELETE /<domain>/whitelists
:return:
"""
req = client.whitelists.delete(domain=domain)
print(req.json())
import os
from mailgun.client import Client
key: str = os.environ["APIKEY"]
domain: str = os.environ["DOMAIN"]
client: Client = Client(auth=("api", key))
def post_routes() -> None:
"""
POST /routes
:return:
"""
data = {
"priority": 0,
"description": "Sample route",
"expression": f"match_recipient('.*@{domain}')",
"action": ["forward('http://myhost.com/messages/')", "stop()"],
}
req = client.routes.create(domain=domain, data=data)
print(req.json())
def get_route_by_id() -> None:
"""
GET /routes/<id>
:return:
"""
req = client.routes.get(domain=domain, route_id="6012d994e8d489e24a127e79")
print(req.json())
import os
from mailgun.client import Client
key: str = os.environ["APIKEY"]
domain: str = os.environ["DOMAIN"]
client: Client = Client(auth=("api", key))
def post_lists() -> None:
"""
POST /lists
:return:
"""
data = {
"address": f"python_sdk2@{domain}",
"description": "Mailgun developers list",
}
req = client.lists.create(domain=domain, data=data)
print(req.json())
def get_lists_members() -> None:
"""
GET /lists/<address>/members/pages
:return:
"""
req = client.lists_members_pages.get(domain=domain, address=mailing_list_address)
print(req.json())
def delete_lists_address() -> None:
"""
DELETE /lists/<address>
:return:
"""
req = client.lists.delete(domain=domain, address=f"python_sdk2@{domain}")
print(req.json())
import os
from mailgun.client import Client
key: str = os.environ["APIKEY"]
domain: str = os.environ["DOMAIN"]
client: Client = Client(auth=("api", key))
def get_domain_templates() -> None:
"""
GET /<domain>/templates
:return:
"""
params = {"limit": 1}
req = client.templates.get(domain=domain, filters=params)
print(req.json())
def update_template() -> None:
"""
PUT /<domain>/templates/<name>
:return:
"""
data = {"description": "new template description"}
req = client.templates.put(data=data, domain=domain, template_name="template.name1")
print(req.json())
def create_new_template_version() -> None:
"""
POST /<domain>/templates/<template>/versions
:return:
"""
data = {
"tag": "v1",
"template": "{{fname}} {{lname}}",
"engine": "handlebars",
"active": "yes",
}
req = client.templates.create(
data=data, domain=domain, template_name="template.name1", versions=True
)
print(req.json())
def get_all_versions() -> None:
"""
GET /<domain>/templates/<template>/versions
:return:
"""
req = client.templates.get(
domain=domain, template_name="template.name1", versions=True
)
print(req.json())
import os
from mailgun.client import Client
key: str = os.environ["APIKEY"]
domain: str = os.environ["DOMAIN"]
client: Client = Client(auth=("api", key))
def update_ippool() -> None:
"""
PATCH /v1/ip_pools/{pool_id}
:return:
"""
data = {
"name": "test_pool3",
"description": "Test3",
}
req = client.ippools.patch(
domain=domain, data=data, pool_id="60140bc1fee3e84dec5abeeb"
)
print(req.json())
def link_ippool() -> None:
"""
POST /v3/domains/{domain_name}/ips
:return:
"""
data = {"pool_id": "60140d220859fda7bab8bb6c"}
req = client.domains_ips.create(domain=domain, data=data)
print(req.json())
import os
from mailgun.client import Client
key: str = os.environ["APIKEY"]
domain: str = os.environ["DOMAIN"]
client: Client = Client(auth=("api", key))
def get_ips() -> None:
"""
GET /ips
:return:
"""
req = client.ips.get(domain=domain, filters={"dedicated": "true"})
print(req.json())
def delete_domain_ip() -> None:
"""
DELETE /domains/<domain>/ips/<ip>
:return:
"""
request = client.domains_ips.delete(domain=domain, ip="161.38.194.10")
print(request.json())
def get_tags() -> None:
"""
GET /<domain>/tags
:return:
"""
req = client.tags.get(domain=domain)
print(req.json())
def get_aggregate_countries() -> None:
"""
GET /<domain>/tags/<tag>/stats/aggregates/countries
:return:
"""
req = client.tags_stats_aggregates_countries.get(
domain=domain, tag_name="September newsletter"
)
print(req.json())
def post_single_validate() -> None:
"""
POST /v4/address/validate
:return:
"""
data = {"address": "test2@gmail.com"}
params = {"provider_lookup": "false"}
req = client.addressvalidate.create(domain=domain, data=data, filters=params)
print(req.json())
def get_all_inbox() -> None:
"""
GET /v3/inbox/tests
:return:
"""
req = client.inbox_tests.get(domain=domain)
print(req.json())
Mailgun loves developers. You can be part of this project!
This Python SDK is a great introduction to the open source world, check out the code!
Feel free to ask anything, and contribute:
If you have suggestions on how to improve the guides, please submit an issue in our Official API Documentation.
FAQs
Python SDK for Mailgun
We found that mailgun demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Socket uncovered npm malware campaign mimicking popular Node.js libraries and packages from other ecosystems; packages steal data and execute remote code.
Research
Socket's research uncovers three dangerous Go modules that contain obfuscated disk-wiping malware, threatening complete data loss.
Research
Socket uncovers malicious packages on PyPI using Gmail's SMTP protocol for command and control (C2) to exfiltrate data and execute commands.