MailThunder
MailThunder is a lightweight and flexible email automation tool for Python. It wraps SMTP and IMAP4 protocols, provides a JSON-based scripting engine and project templates, and makes sending, receiving, and managing email content effortless.
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Authentication
- Scripting Engine
- Project Templates
- Command-Line Interface
- Socket Server
- API Reference
- Project Structure
- License
Features
- SMTP support — Send emails over implicit TLS with Gmail (default) or any SMTP provider, or over STARTTLS (Microsoft 365)
- IMAP4 support — Read, search, and export emails via IMAP4 SSL
- Attachment handling — Automatically detect MIME types for text, image, audio, and binary files
- HTML email — Send HTML-formatted emails with attachments
- JSON scripting engine — Automate email workflows using JSON action files
- Project templates — Scaffold projects with pre-built keyword and executor templates
- Socket server — Control MailThunder remotely via TCP socket commands
- Package manager — Dynamically load Python packages into the scripting executor
- Environment variable auth — Authenticate via config file or OS environment variables
- OAuth2 login — SASL
XOAUTH2for Gmail and Microsoft 365, with refresh-token exchange and a token cache in the standard library - Auto-export — Export all mailbox emails to local files in one call
- Context manager support — Use
withstatement for both SMTP and IMAP connections - Logging — Built-in logging for all operations
Requirements
- Python 3.10 or later
je_action_core, installed with it: the action executor MailThunder shares with APITestka, LoadDensity and FileAutomation (it uses only the Python standard library)
Installation
Stable release:
pip install je_mail_thunder
Development release:
pip install je_mail_thunder_dev
je_mail_thunder_dev follows the dev branch: CI publishes a new version each time a push to dev
passes the tests and changes what the package ships.
Quick Start
Configuration
Before using MailThunder, you need to set up authentication. Create a file named mail_thunder_content.json in your current working directory:
{
"user": "your_email@gmail.com",
"password": "your_app_password"
}
Important: If you are using Gmail, you must use an App Password, not your regular Google account password. You also need to enable IMAP in your Gmail settings.
Sending an Email (SMTP)
from je_mail_thunder import SMTPWrapper
with SMTPWrapper() as smtp:
smtp.later_init() # Log in using config file or env vars
smtp.create_message_and_send(
message_content="Hello from MailThunder!",
message_setting_dict={
"Subject": "Test Email",
"From": "sender@gmail.com",
"To": "receiver@gmail.com"
}
)
Sending an Email with Attachment
from je_mail_thunder import SMTPWrapper
with SMTPWrapper() as smtp:
smtp.later_init()
smtp.create_message_with_attach_and_send(
message_content="Please see the attached file.",
message_setting_dict={
"Subject": "Email with Attachment",
"From": "sender@gmail.com",
"To": "receiver@gmail.com"
},
attach_file="/path/to/file.pdf",
use_html=False # Set True if message_content is HTML
)
Reading Emails (IMAP)
from je_mail_thunder import IMAPWrapper
with IMAPWrapper() as imap:
imap.later_init() # Log in
imap.select_mailbox("INBOX")
emails = imap.mail_content_list()
for mail in emails:
print(f"Subject: {mail['SUBJECT']}")
print(f"From: {mail['FROM']}")
print(f"Body: {mail['BODY'][:100]}...")
Exporting All Emails to Files
from je_mail_thunder import IMAPWrapper
with IMAPWrapper() as imap:
imap.later_init()
imap.select_mailbox("INBOX")
imap.output_all_mail_as_file() # Saves each email as a file named by subject
Authentication
MailThunder logs in with a password or with OAuth2. It reads the JSON config file first, then the environment variables; OAuth2 settings, when present, are used instead of a password.
JSON Config File
Place mail_thunder_content.json in the current working directory:
{
"user": "your_email@gmail.com",
"password": "your_app_password"
}
Environment Variables
Set these environment variables before running your script:
from je_mail_thunder import set_mail_thunder_os_environ
set_mail_thunder_os_environ(
mail_thunder_user="your_email@gmail.com",
mail_thunder_user_password="your_app_password"
)
Or set them in your shell:
export mail_thunder_user="your_email@gmail.com"
export mail_thunder_user_password="your_app_password"
OAuth2 (Google and Microsoft)
Google and Microsoft are retiring password logins for mail. With OAuth2, MailThunder exchanges a refresh token for a
short-lived access token at the provider's token endpoint (standard library only, https required) and logs in with
SASL XOAUTH2. Get the client ID, client secret and refresh token once through the provider's consent flow (a Google
Cloud OAuth client, or a Microsoft Entra app registration); MailThunder does not run that flow.
In mail_thunder_content.json:
{
"user": "you@example.com",
"oauth2": {
"provider": "microsoft",
"client_id": "...",
"client_secret": "...",
"refresh_token": "...",
"tenant": "common"
}
}
Or in the environment: mail_thunder_user plus mail_thunder_oauth2_provider, mail_thunder_oauth2_client_id, mail_thunder_oauth2_client_secret, mail_thunder_oauth2_refresh_token; optionally mail_thunder_oauth2_tenant, mail_thunder_oauth2_scope, mail_thunder_oauth2_token_url, mail_thunder_oauth2_access_token (a token used as given).
| Provider | SMTP | IMAP | Scope it asks for |
|---|---|---|---|
google (default) |
smtp.gmail.com:465, implicit TLS (SMTPWrapper) |
imap.gmail.com |
https://mail.google.com/ |
microsoft |
smtp.office365.com:587, STARTTLS (SMTPStartTLSWrapper) |
outlook.office365.com |
SMTP.Send, IMAP.AccessAsUser.All on https://outlook.office.com/, offline_access |
smtp_instance and imap_instance, and so the MT_smtp_* / MT_imap_* commands, connect to the servers of the
provider the settings name, so action files work with Microsoft too. Access tokens are cached and refreshed a minute
before they expire. Another provider works with token_url (and scope); create the wrappers with its hosts.
Secrets never appear in log lines or error messages.
From Python:
from je_mail_thunder import OAuth2Settings, SMTPStartTLSWrapper, oauth2_token_cache
settings = OAuth2Settings(user="you@contoso.com", provider="microsoft", client_id="...",
client_secret="...", refresh_token="...", tenant="contoso.onmicrosoft.com")
with SMTPStartTLSWrapper() as smtp:
smtp.oauth2_login(settings.user, oauth2_token_cache.access_token(settings))
smtp.create_message_and_send("Hello", {"Subject": "Hi", "From": settings.user, "To": "friend@example.com"})
Scripting Engine
MailThunder includes a JSON-based scripting engine that lets you automate email workflows without writing Python code.
Action JSON Format
Action files use a list of commands. Each command is an array where the first element is the command name and the optional second element contains the arguments:
{
"mail_thunder": [
["command_name"],
["command_name", {"key": "value"}],
["command_name", ["arg1", "arg2"]]
]
}
- Use a dict
{}as the second element for keyword arguments (**kwargs) - Use a list
[]as the second element for positional arguments (*args) - Use only the command name (no second element) for commands with no arguments
Available Script Commands
| Command | Description | Arguments |
|---|---|---|
MT_smtp_later_init |
Initialize and log in to SMTP | None |
MT_smtp_create_message_and_send |
Create and send an email | {"message_content": str, "message_setting_dict": dict} |
MT_smtp_create_message_with_attach_and_send |
Create and send an email with attachment | {"message_content": str, "message_setting_dict": dict, "attach_file": str, "use_html": bool} |
MT_smtp_quit |
Disconnect from SMTP server (old name smtp_quit still accepted) |
None |
MT_imap_later_init |
Initialize and log in to IMAP | None |
MT_imap_select_mailbox |
Select a mailbox | {"mailbox": str, "readonly": bool} (default: INBOX) |
MT_imap_search_mailbox |
Search and get mail details | {"search_str": str, "charset": str} |
MT_imap_mail_content_list |
Get all mail content as list | {"search_str": str, "charset": str} |
MT_imap_output_all_mail_as_file |
Export all emails to files | {"search_str": str, "charset": str} |
MT_imap_quit |
Disconnect from IMAP server | None |
MT_set_mail_thunder_os_environ |
Set auth env vars | {"mail_thunder_user": str, "mail_thunder_user_password": str} |
MT_get_mail_thunder_os_environ |
Get auth env vars | None |
MT_add_package_to_executor |
Load a Python package into executor | ["package_name"] |
Example — Send an email via JSON script:
{
"mail_thunder": [
["MT_smtp_later_init"],
["MT_smtp_create_message_and_send", {
"message_content": "Hello World!",
"message_setting_dict": {
"Subject": "Automated Email",
"To": "receiver@gmail.com",
"From": "sender@gmail.com"
}
}],
["MT_smtp_quit"]
]
}
Example — Read and export all emails:
{
"mail_thunder": [
["MT_imap_later_init"],
["MT_imap_select_mailbox"],
["MT_imap_output_all_mail_as_file"]
]
}
Extending with Custom Commands
You can add your own functions to the scripting executor:
from je_mail_thunder import add_command_to_executor
def my_custom_function(param1, param2):
print(f"Custom: {param1}, {param2}")
add_command_to_executor({"my_command": my_custom_function})
Then use "my_command" in your JSON action files.
Dynamic Package Loading
Load any installed Python package into the executor at runtime:
{
"mail_thunder": [
["MT_add_package_to_executor", ["os"]],
["os_system", ["echo Hello from os.system"]]
]
}
This loads all functions, builtins, and classes from the specified package, prefixed with packagename_.
Package gate. Because MT_add_package_to_executor can load os or subprocess, an action file or socket
client that names them could run anything. The host program decides what may load:
from je_mail_thunder.utils.executor.action_executor import executor
executor.allow_packages("json") # these, and their submodules
executor.set_allow_arbitrary_packages(False) # refuse everything else before importing it
Neither switch is an action command, so an action file cannot open its own gate. A refused package is recorded
as an ExecuteActionException in that action's result. Until the host calls either switch, any package still
loads but raises a DeprecationWarning: a future release will refuse unlisted packages by default.
Warning: Loading packages like
osinto the executor can be a security risk. Only load trusted packages and validate all inputs.
Project Templates
MailThunder can scaffold a project with pre-built templates:
from je_mail_thunder import create_project_dir
create_project_dir() # Creates in current directory
# or
create_project_dir(project_path="/path/to/project", parent_name="MyMailProject")
This creates the following structure:
MyMailProject/
keyword/
keyword1.json # SMTP send email template
keyword2.json # IMAP read and export template
bad_keyword_1.json # Package loading example (security warning)
executor/
executor_one_file.py # Execute a single action file
executor_folder.py # Execute all action files in a directory
executor_bad_file.py # Bad practice example
Command-Line Interface
MailThunder provides a CLI via python -m je_mail_thunder:
# Execute a single JSON action file
python -m je_mail_thunder -e /path/to/action.json
# Execute all JSON action files in a directory
python -m je_mail_thunder -d /path/to/actions/
# Execute a JSON string directly
python -m je_mail_thunder --execute_str '[["MT_smtp_later_init"], ["MT_smtp_quit"]]'
# Create a new project with templates
python -m je_mail_thunder -c /path/to/project
| Flag | Long Flag | Description |
|---|---|---|
-e |
--execute_file |
Execute a single JSON action file |
-d |
--execute_dir |
Execute all JSON action files in a directory |
-c |
--create_project |
Create a project with templates |
--execute_str |
Execute a JSON string directly |
Socket Server
MailThunder includes a TCP socket server that accepts JSON commands remotely:
from je_mail_thunder.utils.socket_server.mail_thunder_socket_server import start_mail_thunder_socket_server
server = start_mail_thunder_socket_server(host="localhost", port=9942)
# Server is now running in a background thread
Sending commands to the server:
import socket
import json
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("localhost", 9942))
# Send an action command
command = json.dumps([["MT_smtp_later_init"], ["MT_smtp_quit"]])
client.send(command.encode("utf-8"))
# Receive response
response = client.recv(8192).decode("utf-8")
print(response)
client.close()
Send "quit_server" to shut down the server.
API Reference
SMTPWrapper
Extends smtplib.SMTP_SSL (through SMTPClientMixin). Default host: smtp.gmail.com, default port: 465.
| Method | Description |
|---|---|
later_init() |
Log in using config file or environment variables (OAuth2 when configured) |
create_message(message_content, message_setting_dict, **kwargs) |
Create an EmailMessage object |
create_message_with_attach(message_content, message_setting_dict, attach_file, use_html=False) |
Create a MIMEMultipart message with attachment |
create_message_and_send(message_content, message_setting_dict, **kwargs) |
Create and immediately send an email |
create_message_with_attach_and_send(message_content, message_setting_dict, attach_file, use_html=False) |
Create and send an email with attachment |
try_to_login_with_env_or_content() |
Attempt login from config or env vars (OAuth2 settings first, then user and password), returns bool |
oauth2_login(user, access_token) |
Log in with SASL XOAUTH2; raises smtplib.SMTPAuthenticationError when refused |
quit() |
Disconnect and close |
Using a different SMTP provider:
from je_mail_thunder import SMTPStartTLSWrapper, SMTPWrapper
# Implicit TLS on another host
smtp = SMTPWrapper(host="smtp.example.com", port=465)
# STARTTLS, e.g. Microsoft 365 (smtp.office365.com:587 is the default)
smtp = SMTPStartTLSWrapper()
SMTPStartTLSWrapper
Extends smtplib.SMTP with the same methods as SMTPWrapper (both mix in SMTPClientMixin). Default host:
smtp.office365.com, default port: 587. It upgrades the connection with STARTTLS (certificate and host name
verified) before anything else is sent, and refuses a server that does not offer it: the connection is closed and
smtplib.SMTPNotSupportedError raised.
IMAPWrapper
Extends imaplib.IMAP4_SSL. Default host: imap.gmail.com.
| Method | Description |
|---|---|
later_init() |
Log in using config file or environment variables (OAuth2 when configured) |
select_mailbox(mailbox="INBOX", readonly=False) |
Select a mailbox, returns bool |
search_mailbox(search_str="ALL", charset=None) |
Search and return raw mail details as list |
mail_content_list(search_str="ALL", charset=None) |
Return parsed mail content as list of dicts |
output_all_mail_as_file(search_str="ALL", charset=None) |
Export all emails to files named by subject |
oauth2_login(user, access_token) |
Log in with SASL XOAUTH2; raises imaplib.IMAP4.error when refused |
quit() |
Close mailbox and logout |
Mail content dict format:
{
"SUBJECT": "Email subject",
"FROM": "sender@example.com",
"TO": "receiver@example.com",
"BODY": "Email body content..."
}
Executor Functions
| Function | Description |
|---|---|
execute_action(action_list) |
Execute a list of action commands |
execute_files(execute_files_list) |
Execute multiple JSON action files |
add_command_to_executor(command_dict) |
Add custom functions to the executor |
read_action_json(file_path) |
Read a JSON action file |
Utility Functions
| Function | Description |
|---|---|
create_project_dir(project_path, parent_name) |
Create a project with templates |
set_mail_thunder_os_environ(mail_thunder_user, mail_thunder_user_password) |
Set authentication environment variables |
get_mail_thunder_os_environ() |
Get authentication environment variables |
read_output_content() |
Read mail_thunder_content.json from cwd |
write_output_content() |
Write content data to mail_thunder_content.json |
get_dir_files_as_list(path) |
Get all files in a directory as list |
OAuth2Settings(user, provider="google", client_id=None, client_secret=None, refresh_token=None, access_token=None, tenant="common", token_url=None, scope=None) |
OAuth2 login settings; OAUTH2_PROVIDERS holds the google and microsoft presets |
oauth2_token_cache.access_token(settings) |
A cached access token, refreshed when it is about to expire (refresh_access_token(settings) always asks the endpoint) |
resolve_oauth2_settings() |
The OAuth2 settings from the config file or the environment, or None |
xoauth2_string(user, access_token) |
The SASL XOAUTH2 initial response |
Project Structure
MailThunder/
je_mail_thunder/
__init__.py # Public API exports
__main__.py # CLI entry point
smtp/
smtp_wrapper.py # SMTPClientMixin, SMTPWrapper, SMTPStartTLSWrapper
imap/
imap_wrapper.py # IMAPWrapper class
utils/
exception/ # Custom exceptions and error tags
executor/ # JSON scripting engine
file_process/ # File utility functions
json/ # JSON file read/write
json_format/ # JSON formatting
lazy_instance/ # Lazy clients that connect on first use
logging/ # Logger instance
oauth2/ # OAuth2 settings, token refresh, XOAUTH2
package_manager/ # Dynamic package loader
project/ # Project template scaffolding
save_mail_user_content/ # Auth config and env var handling
socket_server/ # TCP socket server
test/ # Unit tests
docs/ # Sphinx documentation
License
This project is licensed under the MIT License.
Metadata
Release files for je-mail-thunder-dev 0.0.41
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| je_mail_thunder_dev-0.0.41.tar.gz | 35.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| je_mail_thunder_dev-0.0.41-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 74.0 kB
Release files / je_mail_thunder_dev-0.0.41.tar.gz
| Download URL | je_mail_thunder_dev-0.0.41.tar.gz |
|---|---|
| Size | 35.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8be38c21d5e3876c85d1deff46c48ed7ce17f683cfa4a0e064ff59fd58786aa8
|
|
BLAKE2b-256 checksum How to use checksums |
84632583eb3f5f2ebd4268f95181008050d3202582c824532b5cfa9ad3878068
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / je_mail_thunder_dev-0.0.41-py3-none-any.whl
| Download URL | je_mail_thunder_dev-0.0.41-py3-none-any.whl |
|---|---|
| Size | 38.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
995627ee43353b448dcfe0cc0a614d24bd30e920866df21d116ca42ef4600da5
|
|
BLAKE2b-256 checksum How to use checksums |
05eba3995006d14d64ea597967ed9222e30e147433ce5068428f88bc7dcebccb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|