Testing emails is an important step to make sure things like welcome emails, password resets, or order updates work the way they should. Python makes this easy with its beginner-friendly approach and powerful libraries. In this guide, we’ll break down the basics of email testing with Python so you can catch any issues before your emails land in someone’s inbox.
Set up your project
Python installation: Make sure Python is installed on your local machine or server. You can download and install it from the official Python website. Alternatively, use a development environment like Anaconda, which includes Python and other helpful tools by default. Write and manage your code Using a text editor or IDE, such as Visual Studio Code, PyCharm, or Jupyter Notebook.
testmail.app account: Sign up for a testmail.app account and get your free API key and namespace.
Install the required libraries:
pip install requests
Create a simple project structure for better organization:
email_testing_project/
├── main.py # The main script
├── config.py # Stores API key and namespace details
└── requirements.txt # Tracks required libraries
Create a config.py
file to store sensitive information separately, like your API key and namespace.
# config.py
API_KEY = "YOUR_API_KEY_HERE"
NAMESPACE = "YOUR_NAMESPACE"
BASE_URL = "https://api.testmail.app/api/json"
Send emails to your testmail.app inbox
There are several ways to send emails with Python, depending on your needs and the level of customization required. Here’s a quick overview of the most common methods:
The smtplib
library
Python’s built-in smtplib
library allows you to send emails by connecting directly to an SMTP server. It’s perfect for small projects or when you need full control over the email-sending process. You can customize the SMTP settings, including server, port, and authentication, without needing additional dependencies, making it an easy option for beginners or one-off email tasks.
Third-party email service APIs
Services like SendGrid, Postmark, and Amazon SES offer APIs that simplify email delivery, especially for larger applications. These services handle complexities like retries, bounces, and deliverability, along with features like email tracking and scalability. They are ideal for production-level projects and are easy to integrate into your Python app via HTTP requests or SDKs.
Framework-specific packages (e.g., Django, Flask)
If you're using frameworks like Django or Flask, they offer built-in or third-party packages for seamless email handling within your app. Django’s send_mail()
and Flask’s Flask-Mail extension provide easy-to-use interfaces, with support for features like templates, background jobs, and email queues, which are useful for sending transactional emails like notifications or password resets.
For simplicity, let's start by opening your mail client and sending an email to {namespace}.{tag}@inbox.testmail.app, where {namespace} is the unique namespace assigned to you (see your console) and {tag} can be anything you choose.

Find your email
testmail.app offers two APIs: a simple JSON API and a full-featured GraphQL API (see the comparison table).

For this tutorial, we will use the JSON API.
The JSON API is straightforward and works in any environment using HTTP GET requests. Only two parameters are mandatory:
&apikey=YOUR_APIKEY
&namespace=YOUR_NAMESPACE
To make the output more readable in your browser, add &pretty=true
.
When waiting for new emails, you can use the &livequery=true
parameter to enable "live" queries. This feature allows the API to wait until at least one matching email is found before returning a result. If a match is found, the result is returned immediately. If no match is found within a minute, the API responds with an HTTP 307 redirect, prompting your HTTP client to resend the query. This process continues indefinitely, so it's important to set a timeout in your testing suite to avoid endless waiting.
Add the following code to main.py
to find the email we just sent:
import os
import json
import requests
from config import API_KEY, NAMESPACE, BASE_URL # Import configuration details
def get_email_data(api_key, namespace):
# Define the API parameters
params = {
'apikey': api_key,
'namespace': namespace,
'livequery': 'true' # Enable live queries
}
# Send a GET request to the Testmail API
try:
response = requests.get(url=BASE_URL, params=params)
response.raise_for_status() # Raise an error for bad responses (4xx, 5xx)
# Parse the JSON response
data = response.json()
print(json.dumps(data, indent=4)) # Pretty-print the response
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
# Call the function
if __name__ == "__main__":
get_email_data(API_KEY, NAMESPACE)
Run the project:
python main.py
If everything is set up correctly, the script will fetch the email from the Testmail API and display it in a neatly formatted JSON structure.
Refine the search
Filter emails by Tag or Tag Prefix
Filter by Tag (exact match):
Example: an email sent to [email protected]
will be in the test
namespace and have the john.smith
tag.
Filter by Tag Prefix:
Example: a query filtered by &tag_prefix=john
will match [email protected]
and also match [email protected]
because both tags (john.smith and john.tory) start with “john”.
import json
import requests
from config import API_KEY, NAMESPACE, BASE_URL
# Define the API parameters with filtering options
params = {
'apikey': API_KEY,
'namespace': NAMESPACE,
'livequery': 'true',
'tag': 'john.smith', # Replace 'john.smith' with your exact tag
'tag_prefix': 'john' # Replace 'john' with your desired tag prefix
}
# Send a GET request to the Testmail API
try:
response = requests.get(url=BASE_URL, params=params)
response.raise_for_status() # Raise an error for bad responses (4xx, 5xx)
# Parse and pretty-print the JSON response
data = response.json()
print(json.dumps(data, indent=4))
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
Retrieve emails received after a specific time
Update the script to include optional parameters for filtering emails based on time. You can filter emails received after a specific timestamp (timestamp_from
) or within a specific time range by using both timestamp_from
and timestamp_to
. These timestamps should be in Unix format, measured in milliseconds since January 1, 1970 (UTC).
import json
import requests
from config import API_KEY, NAMESPACE, BASE_URL
def get_email_data():
# Define the API parameters directly, including timestamp filters
params = {
'apikey': API_KEY,
'namespace': NAMESPACE,
'livequery': 'true',
'timestamp_from': 1672531200000, # Example: Jan 1, 2023 00:00:00 UTC in milliseconds
'timestamp_to': 1672617600000 # Example: Jan 2, 2023 00:00:00 UTC in milliseconds
}
# Send a GET request to the Testmail API
try:
response = requests.get(url=BASE_URL, params=params)
response.raise_for_status() # Raise an error for bad responses (4xx, 5xx)
# Parse the JSON response
data = response.json()
print(json.dumps(data, indent=4)) # Pretty-print the response
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
# Example usage
if __name__ == "__main__":
print("Fetching emails with specific timestamps...")
get_email_data()
Limit the number of emails in the response
Pagination is essential when dealing with large sets of data, like email queries, as it helps avoid overwhelming your system or API with excessive data at once. It ensures efficient data handling by breaking down results into smaller, manageable chunks.The count
in the return object shows the total number of emails that matched your query. Using the limit
and offset
parameters, you can retrieve these results in chunks. For example:
- Start with
limit = 10
andoffset = 0
to fetch the first 10 emails. - Update to
offset = 10
to skip the first 10 and fetch the next set of results. - Repeat until all emails are retrieved.
import json
import requests
from config import API_KEY, NAMESPACE, BASE_URL
def get_email_data():
# Define the API parameters with default values for limit and offset
params = {
'apikey': API_KEY,
'namespace': NAMESPACE,
'livequery': 'true',
'limit': 10, # Default to fetching 10 emails. Adjust as needed.
'offset': 0 # Default to starting from the first email. Adjust as needed.
}
# Send a GET request to the Testmail API
try:
response = requests.get(url=BASE_URL, params=params)
response.raise_for_status() # Raise an error for bad responses (4xx, 5xx)
# Parse the JSON response
data = response.json()
print(json.dumps(data, indent=4)) # Pretty-print the response
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
# Example usage
if __name__ == "__main__":
print("Fetching emails with default limit and offset...")
get_email_data()
Spam test your email
Testmail uses SpamAssassin, a widely recognized open-source anti-spam platform, to test emails. Spam scores and reports can vary depending on the rule sets and plugins enabled by different mailbox providers, as each provider may customize their configuration. Generally, spam scores below 5 are not a concern. However, if the score exceeds 5, it's important to review the triggered rules in the spam report and address the most significant issues.
import json
import requests
from config import API_KEY, NAMESPACE, BASE_URL
def get_spam_report():
# Define the API parameters
params = {
'apikey': API_KEY,
'namespace': NAMESPACE,
'livequery': 'true',
'spam_report': 'true' # Include spam report in the email object
}
# Send a GET request to the Testmail API
try:
response = requests.get(url=BASE_URL, params=params)
response.raise_for_status() # Raise an error for bad responses (4xx, 5xx)
# Parse and pretty-print the JSON response
data = response.json()
print(json.dumps(data, indent=4))
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
# Example usage
if __name__ == "__main__":
print("Fetching emails with spam report included...")
get_spam_report()