Bulk Email Sender
A modular, robust, and state-persisting mass email sender library for Python. It supports multiple sender accounts with round-robin rotation, personalized recipient names, automatic daily safety caps, cooldown intervals between emails, attachments, and automatic daily counter resets.
Quick Start
pip install bulk-email-sender
Usage
CLI
# Run the campaign (reads config.json from the current directory)
bulk-email-sender
# Reset progress state and start over
bulk-email-sender --reset
# Or run as a module
python -m bulk_email_sender [--reset]
Programmatic
The library never configures logging handlers — it only emits log records via the standard logging module. Set up your own handlers before calling run():
import logging
from bulk_email_sender import BulkEmailSender
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler("/var/log/campaign.log", encoding="utf-8"),
logging.StreamHandler(),
],
)
body = """I hope this message finds you well.
I am writing to express my interest in potential job opportunities in IT.
Please find attached my curriculum vitae for your consideration.
Thank you for your time and review.
"""
config = {
"provider": "gmail",
"senders": [
{
"email": "you@gmail.com",
"password": "your-app-password",
"name": "Name",
},
{
"email": "you@yahoo.com",
"password": "your-app-password-2",
"name": "Name",
"provider": "yahoo",
},
],
"leads_path": "leads.txt", # .txt or .csv
"subject": "Your Subject",
"body": body,
"attachment_path": "resume.pdf", # optional, leave "" for none
"interval_seconds": 60,
"daily_limit": 450,
"state_file_path": "/var/log/campaign.json", # optional, defaults to ./email_state.json
}
BulkEmailSender(config).run()
Note: Environment variables
GMAIL_SENDERandGMAIL_APP_PASSWORDoverride the"senders"list at runtime — useful for CI/containerised deployments.
Configuration Reference
| Key | Type | Description |
|---|---|---|
provider |
string |
Default SMTP provider for all senders. See providers. |
senders |
array |
List of sender accounts. At least one is required. |
senders[].email |
string |
Sender email address. |
senders[].password |
string |
App password (not your login password). |
senders[].name |
string |
Display name shown to recipients. |
senders[].provider |
string |
(optional) Overrides the global provider for this account. |
leads_path |
string |
Path to the recipients file (.txt or .csv). |
subject |
string |
Email subject line. |
body |
string |
Required. The email body as a plain string. Line breaks (\n) are automatically converted to <br> in HTML. |
attachment_path |
string |
(optional) Path to a file to attach. Leave "" for none. |
interval_seconds |
number |
Seconds to wait between emails. |
daily_limit |
number |
Maximum emails to send per day. |
log_path |
string |
(optional) Relative or absolute path for the log file. Defaults to ./email_sender.log. |
state_file_path |
string |
(optional) Relative or absolute path for the state file. Defaults to ./email_state.json. |
SMTP Provider Presets
Built-in presets automatically configure the host and port for the chosen "provider":
| Provider Name | Default Host | Port |
|---|---|---|
"gmail" |
smtp.gmail.com |
587 |
"yahoo" |
smtp.mail.yahoo.com |
587 |
"icloud" |
smtp.mail.me.com |
587 |
"ZOHO" |
smtp.zoho.com |
587 |
"GMX" |
smtp.gmx.com |
587 |
"YANDEX" |
smtp.yandex.com |
587 |
"AOL" |
smtp.aol.com |
587 |
Individual senders can override the global provider by setting their own "provider", "smtp_host", or "smtp_port" keys inside the senders entry. The library will automatically reconnect with the correct credentials when the active sender changes.
Round-Robin Account Rotation
When multiple accounts are listed in "senders", the library rotates through them in round-robin order (1st email → account 1, 2nd email → account 2, 3rd email → account 1, …). If the rotation switches accounts, the SMTP connection is automatically re-established with the new credentials.
Defining Recipient Leads
Plain Text (.txt)
One recipient per line. Name is optional and can be separated by , or ;:
another-recipient@example.com
Jane Doe; jane.doe@example.com
CSV (.csv)
Requires an email column. An optional name column enables personalised greetings:
name,email
Jane,jane.doe@gmail.com
,another-recipient@example.com
When a name is present, the greeting becomes "Hello Jane,". When absent, it defaults to "Hello,".
Email Attachments (Optional)
Set "attachment_path" to a file path to attach it to every email.
- Supported formats: Any file type (
.pdf,.docx,.xlsx,.zip, …). - Size limit: Files larger than 25 MB will abort the campaign before it starts.
- Missing files: If the path does not exist, the campaign is aborted with an error.
- No attachment: Leave
"attachment_path": ""or omit the key entirely.
Metadata
Release files for bulk-email-sender 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bulk_email_sender-0.3.0.tar.gz | 14.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bulk_email_sender-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 28.8 kB
Release files / bulk_email_sender-0.3.0.tar.gz
| Download URL | bulk_email_sender-0.3.0.tar.gz |
|---|---|
| Size | 14.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ea2acf236669cdf480477f65f1ca3d40ade4028f51b4d3f7dec9373a94d240a1
|
|
BLAKE2b-256 checksum How to use checksums |
40494030a8172ebbe21f01b116327bd0b8df32137e9a147a7c133d911bdaa814
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.
Transparency logRelease files / bulk_email_sender-0.3.0-py3-none-any.whl
| Download URL | bulk_email_sender-0.3.0-py3-none-any.whl |
|---|---|
| Size | 14.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9b0d2af15deaa8087d3f6720852b42d1d99040db444879384944315a0545d1c9
|
|
BLAKE2b-256 checksum How to use checksums |
bddd50de8224f42eb0c53d6d182e02939afbe2af071fd0347058a067b044ab21
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.
Transparency log