API Reference¶
FluxMail¶
FluxMail(object_type: Union[EmailObject, str], host: Union[EmailInstance, str], logger: Optional[logging.Logger] = None, log_level: str = 'WARNING', port: int = 25, username: Optional[str] = None, password: Optional[str] = None, use_tls: bool = False, use_ssl: bool = False, ssl_context: Optional[ssl.SSLContext] = None, timeout: int = 30, max_retries: int = 0, retry_delay: float = 1.0)
¶
Automates email creation and sending using SMTP or Outlook.
| PARAMETER | DESCRIPTION |
|---|---|
|
Email object type:
TYPE:
|
|
SMTP relay host. Accepts an
TYPE:
|
|
Logger instance for logging.
TYPE:
|
|
Logging level string (e.g.
TYPE:
|
|
SMTP port. Default:
TYPE:
|
|
SMTP login username. When this is a valid email address it also
serves as the default
TYPE:
|
|
SMTP login password.
TYPE:
|
|
Enable STARTTLS for the SMTP connection. Default:
TYPE:
|
|
Enable implicit TLS (SSL) for the SMTP connection (port 465). Default:
TYPE:
|
|
Custom SSL context for TLS/SSL connections. Default:
TYPE:
|
|
Connection timeout in seconds. Default:
TYPE:
|
|
Number of retry attempts on send failure.
TYPE:
|
|
Seconds to wait between retry attempts. Default:
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
FluxMailException
|
If |
TypeError
|
If |
| METHOD | DESCRIPTION |
|---|---|
create |
Creates an email with the specified details. |
display |
Displays or returns an email preview. |
send |
Sends or previews the email. |
send_async |
Sends or previews the email asynchronously (SMTP only). |
from_env |
Create a |
test_connection |
Test SMTP connectivity and authentication without sending email. |
Source code in src/fluxmail/fluxmail.py
| Python | |
|---|---|
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | |
create(subject: str, recipients: List[str], body: str, sender: Optional[str] = None, cc: Optional[List[str]] = None, bcc: Optional[List[str]] = None, reply_to: Optional[str] = None, attachments: Optional[List[str]] = None, html_body: bool = False, plain_body: Optional[str] = None, in_reply_to: Optional[str] = None, references: Optional[List[str]] = None, priority: Optional[str] = None, unsubscribe_url: Optional[str] = None, inline_images: Optional[Dict[str, str]] = None, inline_css: bool = False) -> FluxMail
¶
Creates an email with the specified details.
| PARAMETER | DESCRIPTION |
|---|---|
|
Email subject.
TYPE:
|
|
Recipient email addresses.
TYPE:
|
|
Email content (plain text or HTML).
TYPE:
|
|
Sender address (SMTP only). Defaults to
TYPE:
|
|
CC'd email addresses.
TYPE:
|
|
BCC'd email addresses (SMTP only).
TYPE:
|
|
Reply-To address (SMTP only).
TYPE:
|
|
Paths to files to attach.
TYPE:
|
|
Use HTML format. Default:
TYPE:
|
|
Plain-text fallback body. When provided alongside
TYPE:
|
|
Message-ID of the email being replied to (SMTP only).
TYPE:
|
|
List of Message-IDs forming the thread chain (SMTP only).
TYPE:
|
|
Message priority:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
FluxMail
|
Returns |
Source code in src/fluxmail/fluxmail.py
| Python | |
|---|---|
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 | |
display() -> str
¶
Displays or returns an email preview.
| RETURNS | DESCRIPTION |
|---|---|
str
|
Email preview string. |
| RAISES | DESCRIPTION |
|---|---|
FluxMailException
|
If |
Source code in src/fluxmail/fluxmail.py
send(dry_run: bool = False) -> str
¶
Sends or previews the email.
| PARAMETER | DESCRIPTION |
|---|---|
|
If
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
Success or preview message. |
| RAISES | DESCRIPTION |
|---|---|
FluxMailException
|
If |
Source code in src/fluxmail/fluxmail.py
send_async(dry_run: bool = False) -> str
async
¶
Sends or previews the email asynchronously (SMTP only).
Note: retry (max_retries) is not supported for async sends.
Source code in src/fluxmail/fluxmail.py
from_env() -> FluxMail
classmethod
¶
Create a FluxMail instance from environment variables.
Reads FLUXMAIL_TYPE, FLUXMAIL_HOST, FLUXMAIL_PORT,
FLUXMAIL_USERNAME, FLUXMAIL_PASSWORD, FLUXMAIL_TLS,
FLUXMAIL_SSL, FLUXMAIL_TIMEOUT, FLUXMAIL_MAX_RETRIES,
FLUXMAIL_RETRY_DELAY.
| RAISES | DESCRIPTION |
|---|---|
FluxMailException
|
If |
Source code in src/fluxmail/fluxmail.py
test_connection() -> dict
¶
Test SMTP connectivity and authentication without sending email.
| RETURNS | DESCRIPTION |
|---|---|
dict
|
|
| RAISES | DESCRIPTION |
|---|---|
FluxMailException
|
|
Source code in src/fluxmail/fluxmail.py
Constructor¶
FluxMail(
object_type,
host,
port=25,
use_tls=False,
use_ssl=False,
ssl_context=None,
timeout=30,
max_retries=0,
retry_delay=1.0,
username=None,
password=None,
sender=None,
log_level="WARNING",
logger=None,
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
object_type |
str \| EmailObject |
— | "smtp" or "outlook" (or EmailObject.SMTP / EmailObject.OUTLOOK) |
host |
str \| EmailInstance |
— | Relay hostname string, "relay:domain" shorthand, or EmailInstance |
port |
int |
25 |
SMTP port. Use 587 for STARTTLS, 465 for implicit TLS. |
use_tls |
bool |
False |
Enable STARTTLS negotiation (port 587). Mutually exclusive with use_ssl. |
use_ssl |
bool |
False |
Use implicit TLS via smtplib.SMTP_SSL (port 465). Mutually exclusive with use_tls. |
ssl_context |
ssl.SSLContext \| None |
None |
Custom SSL context for certificate customisation (e.g. self-signed certs). Passed to both use_tls and use_ssl paths. |
timeout |
int |
30 |
SMTP connection timeout in seconds. |
max_retries |
int |
0 |
Number of additional attempts after the first send failure. 0 disables retry. Applied to send() only. |
retry_delay |
float |
1.0 |
Seconds to wait between retry attempts (tenacity wait_fixed). |
username |
str \| None |
None |
SMTP login username. When a valid email, doubles as the From address. |
password |
str \| None |
None |
SMTP login password |
sender |
str \| None |
None |
Explicit From address. Overrides username. |
log_level |
str |
"WARNING" |
Logging level: "DEBUG", "INFO", "WARNING", "ERROR" |
logger |
logging.Logger \| None |
None |
Supply a custom logger instance |
create()¶
Build the email message. Returns self for method chaining.
create(
subject,
recipients,
body,
sender=None,
cc=None,
bcc=None,
attachments=None,
html_body=False,
plain_body=None,
reply_to=None,
in_reply_to=None,
references=None,
priority=None,
unsubscribe_url=None,
inline_images=None,
inline_css=False,
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
subject |
str |
— | Email subject line |
recipients |
List[str] |
— | One or more To addresses |
body |
str |
— | Email body — plain text or HTML string |
sender |
str \| None |
None |
Override the From address set on the constructor |
cc |
List[str] \| None |
None |
CC addresses |
bcc |
List[str] \| None |
None |
BCC addresses |
attachments |
List[str] \| None |
None |
File paths to attach (MIME type auto-detected) |
html_body |
bool |
False |
Treat body as HTML |
plain_body |
str \| None |
None |
Plain-text fallback when html_body=True (sends multipart/alternative) |
reply_to |
str \| None |
None |
Reply-To header (SMTP only) |
in_reply_to |
str \| None |
None |
In-Reply-To header for threading |
references |
List[str] \| None |
None |
References header for threading |
priority |
str \| None |
None |
"high", "normal", or "low" |
unsubscribe_url |
str \| None |
None |
HTTPS URL added as List-Unsubscribe + List-Unsubscribe-Post headers (RFC 8058, SMTP only) |
inline_images |
Dict[str, str] \| None |
None |
Map of cid_name → file_path for inline images (requires html_body=True, SMTP only) |
inline_css |
bool |
False |
Run premailer.transform() on the HTML body to inline CSS rules (silently skipped when html_body=False) |
send()¶
Send the email. Returns self.
| Parameter | Type | Default | Description |
|---|---|---|---|
dry_run |
bool |
False |
If True, returns a preview string without sending (delegates to display()) |
send_async()¶
Async equivalent of send(). Delegates to aiosmtplib under the hood.
max_retries is not applied — retry in the caller if needed.
| Parameter | Type | Default | Description |
|---|---|---|---|
dry_run |
bool |
False |
If True, returns a preview string without sending (delegates to display()) |
import asyncio, os
from fluxmail import FluxMail
async def main():
email = FluxMail(
object_type="smtp",
host="smtp.gmail.com",
port=587,
use_tls=True,
username=os.environ["FLUXMAIL_USERNAME"],
password=os.environ["FLUXMAIL_PASSWORD"],
)
email.create(
subject="Hello",
recipients=["user@example.com"],
body="Sent asynchronously.",
)
result = await email.send_async()
print(result) # "Email sent successfully via SMTP."
asyncio.run(main())
from fastapi import FastAPI
from fluxmail import FluxMail
import os
app = FastAPI()
mailer = FluxMail(
object_type="smtp",
host="smtp.gmail.com",
port=587,
use_tls=True,
username=os.environ["FLUXMAIL_USERNAME"],
password=os.environ["FLUXMAIL_PASSWORD"],
)
@app.post("/notify")
async def notify(user_email: str, message: str):
mailer.create(subject="Notification", recipients=[user_email], body=message)
return {"status": await mailer.send_async()}
display()¶
Return an email preview string (SMTP), or open the Outlook compose window (Outlook).
is_smtp() / is_outlook()¶
Convenience predicates for the configured protocol.
email = FluxMail(object_type="smtp", host="smtp.gmail.com", ...)
email.is_smtp() # True
email.is_outlook() # False
from_env()¶
Create a FluxMail instance from FLUXMAIL_* environment variables.
Reads FLUXMAIL_TYPE, FLUXMAIL_HOST, FLUXMAIL_PORT, FLUXMAIL_USERNAME,
FLUXMAIL_PASSWORD, FLUXMAIL_TLS, FLUXMAIL_SSL, FLUXMAIL_TIMEOUT,
FLUXMAIL_MAX_RETRIES, FLUXMAIL_RETRY_DELAY. See the
configuration guide for
the full variable reference.
Raises FluxMailException(code="invalid_config") when FLUXMAIL_HOST is missing
and FLUXMAIL_TYPE=smtp.
test_connection()¶
Open, authenticate, and immediately close an SMTP connection — without sending any email. Use for startup health checks and debugging credentials.
Returns {"ok": True, "relay": str, "port": int, "latency_ms": int} on success.
Raises FluxMailException(code="connection_failed") on network or auth failure;
FluxMailException(code="outlook_no_connect") for Outlook instances.
try:
info = mailer.test_connection()
print(f"Connected in {info['latency_ms']}ms")
except FluxMailException as e:
print(f"Failed: {e}") # e.code == "connection_failed"
Context manager — connection reuse¶
Use FluxMail as a context manager to hold one SMTP connection open across multiple sends.
Useful for bulk sends where opening a new connection per message would be slow.
from fluxmail import FluxMail
recipients = ["a@example.com", "b@example.com", "c@example.com"]
with FluxMail(
object_type="smtp",
host="smtp.gmail.com",
port=587,
use_tls=True,
username="me@gmail.com",
password="secret",
) as mailer:
for recipient in recipients:
mailer.create(
subject="Hello",
recipients=[recipient],
body="Hi there!",
).send()
from fluxmail import FluxMail, FluxMailException
with FluxMail(object_type="smtp", host="smtp.gmail.com", port=587,
use_tls=True, username="me@gmail.com", password="secret") as mailer:
for recipient in recipients:
try:
mailer.create(subject="Hi", recipients=[recipient], body="Hello").send()
except FluxMailException as e:
print(f"Failed to send to {recipient}: {e}")
EmailTemplate¶
Jinja2-based email body renderer. Separates template content from sending logic.
| Parameter | Type | Default | Description |
|---|---|---|---|
template |
str |
— | Jinja2 template string |
autoescape |
bool |
False |
HTML-escape all variables. Use True for HTML emails. |
render()¶
Returns the rendered string with all {{ variable }} placeholders replaced.
from_file()¶
Load a template from a UTF-8 file.
tmpl = EmailTemplate.from_file("templates/welcome.html", autoescape=True)
body = tmpl.render(first_name="Alice", company="Acme")
BulkSender¶
Sends a batch of emails over a single persistent SMTP connection. Significantly faster than reconnecting for each message.
| Parameter | Type | Description |
|---|---|---|
mailer |
FluxMail |
Configured FluxMail instance (SMTP only) |
send_batch()¶
| Parameter | Type | Default | Description |
|---|---|---|---|
messages |
list[dict] |
— | Each dict is unpacked as **kwargs into FluxMail.create() |
on_success |
Callable[[int, str], None] \| None |
None |
Called with (index, result_string) after each successful send |
on_error |
Callable[[int, FluxMailException], None] \| None |
None |
Called with (index, exception) after each failed send |
progress |
bool |
True |
Show a Rich progress bar in the terminal |
Returns a dict with keys:
| Key | Type | Description |
|---|---|---|
sent |
int |
Number of messages sent successfully |
failed |
int |
Number of messages that raised FluxMailException |
total |
int |
Total messages in the batch |
errors |
list[tuple[int, FluxMailException]] |
(index, exception) for each failure |
import os
from fluxmail import FluxMail, BulkSender
mailer = FluxMail(
object_type="smtp",
host="smtp.gmail.com",
port=587,
use_tls=True,
username=os.environ["FLUXMAIL_USERNAME"],
password=os.environ["FLUXMAIL_PASSWORD"],
)
messages = [
{"subject": f"Hi {name}", "recipients": [email], "body": f"Hello {name}!"}
for name, email in [
("Alice", "alice@example.com"),
("Bob", "bob@example.com"),
]
]
result = BulkSender(mailer).send_batch(messages)
print(result["sent"], "sent,", result["failed"], "failed")
from fluxmail import BulkSender, FluxMailException
def on_success(i: int, result: str) -> None:
print(f"[{i}] OK")
def on_error(i: int, exc: FluxMailException) -> None:
print(f"[{i}] FAILED ({exc.code}): {exc}")
result = BulkSender(mailer).send_batch(
messages,
on_success=on_success,
on_error=on_error,
progress=False,
)
for idx, exc in result["errors"]:
print(f" Message {idx}: {exc}")
send_batch_async()¶
Async equivalent of send_batch(). Opens one persistent async SMTP connection
for the entire batch using aiosmtplib.
await send_batch_async(
messages,
*,
on_success=None,
on_error=None,
progress=True,
max_per_second=0,
) -> dict
| Parameter | Type | Default | Description |
|---|---|---|---|
messages |
list[dict] |
— | Each dict is unpacked as **kwargs into FluxMail.create() |
on_success |
Callable[[int, str], None] \| None |
None |
Called with (index, result_string) after each successful send |
on_error |
Callable[[int, FluxMailException], None] \| None |
None |
Called with (index, exception) after each failed send |
progress |
bool |
True |
Show a Rich progress bar in the terminal |
max_per_second |
float |
0 |
Maximum sends per second; 0 disables rate limiting. Negative values raise FluxMailException. |
Returns the same {"sent", "failed", "total", "errors"} dict as send_batch().
FluxMailBackend (Django)¶
Drop-in EMAIL_BACKEND for Django. Reads Django's standard EMAIL_* settings
and delegates send to FluxMail's SMTP transport.
# settings.py
EMAIL_BACKEND = "fluxmail.backends.django.FluxMailBackend"
EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_HOST_USER = "me@gmail.com"
EMAIL_HOST_PASSWORD = "secret"
EMAIL_USE_TLS = True
EMAIL_TIMEOUT = 30
# Optional
EMAIL_FLUXMAIL_MAX_RETRIES = 3
EMAIL_FLUXMAIL_RETRY_DELAY = 1.0
| Django setting | FluxMail param | Default |
|---|---|---|
EMAIL_HOST |
host |
"localhost" |
EMAIL_PORT |
port |
25 |
EMAIL_HOST_USER |
username |
None |
EMAIL_HOST_PASSWORD |
password |
None |
EMAIL_USE_TLS |
use_tls |
False |
EMAIL_USE_SSL |
use_ssl |
False |
EMAIL_TIMEOUT |
timeout |
30 |
EMAIL_FLUXMAIL_MAX_RETRIES |
max_retries |
0 |
EMAIL_FLUXMAIL_RETRY_DELAY |
retry_delay |
1.0 |
Once configured, all standard Django email functions (send_mail, EmailMessage,
EmailMultiAlternatives) route through FluxMail automatically. Django's own MIME
builder handles attachments and multipart — no re-implementation.
Thread safety
A single FluxMailBackend instance uses one shared FluxMail object. Do not
use the same backend instance concurrently from multiple threads without
external synchronisation.
EmailInstance¶
A namedtuple representing an SMTP relay configuration.
| Field | Type | Default | Description |
|---|---|---|---|
relay |
str |
— | Hostname of the SMTP relay (e.g. smtp.gmail.com) |
domain |
str |
"" |
Email domain for the relay (e.g. gmail.com). Optional. |
EmailInstance = namedtuple('EmailInstance', ['relay', 'domain'], defaults=[''])
module-attribute
¶
Host formats¶
host on the FluxMail constructor accepts three forms:
EmailObject¶
EmailObject
¶
from fluxmail import EmailObject
FluxMail(object_type=EmailObject.SMTP, ...)
FluxMail(object_type=EmailObject.OUTLOOK, ...)
# String aliases also work:
FluxMail(object_type="smtp", ...)
FluxMail(object_type="outlook", ...)
FluxMailException¶
FluxMailException(message: str, code: Optional[str] = None)
¶
All errors raised by FluxMail are FluxMailException instances. Every exception
carries an optional code attribute for programmatic error handling.
exc.code |
Raised when |
|---|---|
"not_created" |
send() / send_async() / display() called before create() |
"no_relay" |
SMTP relay hostname is empty |
"sender_required" |
sender= not provided and username is not a valid email |
"invalid_config" |
use_ssl=True + use_tls=True together, or invalid logger |
"invalid_params" |
Empty subject, non-list recipients/cc/bcc/attachments |
"invalid_priority" |
priority value not "high", "normal", or "low" |
"invalid_email" |
Email address fails format validation |
"no_email" |
Empty string passed to validate_email() |
"attachment_not_found" |
Attachment path does not exist |
"read_error" |
Cannot read an attachment file |
"send_failed" |
SMTP send failure (after all retries) |
"display_failed" |
display() raised an unexpected error |
"outlook_no_send" |
send() called on an Outlook instance |
"outlook_no_async" |
send_async() called on an Outlook instance |
"outlook_no_sender" |
sender= set on an Outlook instance |
None |
Any raise without an assigned code |
A single try/except covers both create() and send():
from fluxmail import FluxMail, FluxMailException
try:
FluxMail(
object_type="smtp",
host="smtp.gmail.com",
port=587,
use_tls=True,
username="me@gmail.com",
password="secret",
).create(
subject="Test",
recipients=["user@example.com"],
body="Hello",
).send()
except FluxMailException as e:
print(f"Email failed: {e}")
# SendGrid uses "apikey" as username — not a valid email address.
# Pass sender= explicitly or FluxMailException is raised.
from fluxmail import FluxMail, FluxMailException
try:
FluxMail(
object_type="smtp",
host="smtp.sendgrid.net",
port=587,
use_tls=True,
username="apikey",
password="SG.xxxx",
# sender= required here
).create(subject="Hi", recipients=["user@example.com"], body="Hello").send()
except FluxMailException as e:
print(e) # "sender is required. Pass sender= explicitly..."
from fluxmail import FluxMail, EmailInstance, EmailObject, FluxMailException
try:
FluxMail(
object_type=EmailObject.OUTLOOK,
host=EmailInstance(relay=""),
).create(subject="Hi", recipients=["user@example.com"], body="Hello").send()
except FluxMailException as e:
print(e) # "Outlook is not supported on this platform."
Full examples¶
import os
from fluxmail import FluxMail
FluxMail(
object_type="smtp",
host="smtp.gmail.com",
port=587,
use_tls=True,
username=os.environ["FLUXMAIL_USERNAME"],
password=os.environ["FLUXMAIL_PASSWORD"],
).create(
subject="Hello from FluxMail",
recipients=["friend@example.com"],
body="Hi there!",
).send()
import os
from fluxmail import FluxMail
FluxMail(
object_type="smtp",
host="smtp.sendgrid.net",
port=587,
use_tls=True,
username="apikey",
password=os.environ["SENDGRID_API_KEY"],
sender="noreply@myapp.com",
).create(
subject="Your order has shipped",
recipients=["customer@example.com"],
body="<h1>Order Shipped</h1><p>Your order is on its way.</p>",
html_body=True,
plain_body="Your order is on its way.",
).send()
import os
from fluxmail import FluxMail
subscribers = ["alice@example.com", "bob@example.com", "carol@example.com"]
with FluxMail(
object_type="smtp",
host="smtp.gmail.com",
port=587,
use_tls=True,
username=os.environ["FLUXMAIL_USERNAME"],
password=os.environ["FLUXMAIL_PASSWORD"],
) as mailer:
for email_addr in subscribers:
mailer.create(
subject="Monthly Newsletter",
recipients=[email_addr],
body="<h1>Newsletter</h1>",
html_body=True,
).send()
import os
from fluxmail import FluxMail
email = FluxMail(
object_type="smtp",
host="smtp.gmail.com",
port=587,
use_tls=True,
username=os.environ["FLUXMAIL_USERNAME"],
password=os.environ["FLUXMAIL_PASSWORD"],
)
email.create(
subject="Monthly Report",
recipients=["alice@example.com", "bob@example.com"],
body="<h1>Report</h1><p>See attached.</p>",
html_body=True,
plain_body="Monthly report — see attached.",
sender="reports@myapp.com",
cc=["manager@example.com"],
bcc=["archive@example.com"],
attachments=["/path/to/report.pdf"],
reply_to="noreply@myapp.com",
priority="high",
)
email.send()