Article featured image
Test emails with Python: A beginner’s guide
6min readLast updated: March 28, 2025

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.

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 and offset = 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()

Subscribe to blog

Stay updated with our latest insights and curated articles delivered straight to your inbox.