Single backend (EMAIL_BACKEND)
Section titled “Single backend (EMAIL_BACKEND)”Configure one global backend in settings:
# SMTP (production)EMAIL_BACKEND = "buraq.contrib.email.backends.smtp.SMTPEmailBackend"EMAIL_HOST = "smtp.gmail.com"EMAIL_PORT = 587EMAIL_USE_TLS = TrueEMAIL_HOST_USER = "you@gmail.com"EMAIL_HOST_PASSWORD = "your-app-password"DEFAULT_FROM_EMAIL = "you@gmail.com"
# File backend (development — writes emails to disk)EMAIL_BACKEND = "buraq.contrib.email.backends.file.FileEmailBackend"EMAIL_FILE_PATH = "./sent_emails"Multiple Mailers
Section titled “Multiple Mailers”Use MAILERS when you need distinct delivery channels — e.g. a transactional SMTP relay for account emails and a bulk relay for newsletters:
MAILERS = { "default": { "BACKEND": "buraq.contrib.email.backends.smtp.SMTPEmailBackend", "HOST": "smtp.example.com", "PORT": 587, "HOST_USER": "noreply@example.com", "HOST_PASSWORD": "secret", "USE_TLS": True, }, "bulk": { "BACKEND": "buraq.contrib.email.backends.smtp.SMTPEmailBackend", "HOST": "bulk.relay.com", "PORT": 587, "HOST_USER": "bulk@example.com", "HOST_PASSWORD": "secret", "USE_TLS": True, },}Select a mailer with the using= argument on any send function:
await send_mail("Welcome!", "Thanks for signing up.", ["user@example.com"], using="default")await send_mail("Newsletter", body, recipients, using="bulk")using=None (the default) falls back to the legacy EMAIL_BACKEND setting.
get_connection()
Section titled “get_connection()”Retrieve a backend instance directly:
from buraq.contrib.email import get_connection
conn = get_connection(using="bulk")await conn.send(message)Backends are cached after the first call — no new connections on repeated calls, so there is no connection object to open, pass around and close. To send several messages over one connection, hand them all to the backend at once:
conn = get_connection(using="bulk")sent = await conn.send_many([first, second, third])send_mass_mail() is the same thing for plain-text messages given as tuples.
The SMTP backend opens one connection for the batch, authenticates once, and sends every message through it. A message the server rejects costs that message, not the batch; the return value is how many were accepted.
Why not a loop of send()
Section titled “Why not a loop of send()”send() opens a connection, negotiates TLS, authenticates and quits — every
time. It is the right shape for one message and the wrong shape for fifty:
for user in users: await send_mail("Hello", body, [user.email]) # fifty handshakesBuild the list and hand it over instead:
messages = [ EmailMessage(subject="Hello", body=body, to=[user.email]) for user in users if user.wants_email]await get_connection().send_many(messages) # one handshakeThe filtering that would have sat inside the loop moves into building the list.
Working between sends
Section titled “Working between sends”When the messages are not all known up front — you are reading rows, deciding as you go, or pausing between them — open a connection and send into it:
async with get_connection().open() as connection: async for row in Subscriber.objects.iterator(): if not await row.should_receive(digest): continue await connection.send(build_message(row))One handshake for the whole block, however long the loop runs and whatever happens in between. The connection closes on the way out, including when the block raises.
Each open() returns its own connection, so two blocks running at once do not
share one — get_connection() caches backends, and a connection kept on the
backend would be closed by whichever block finished first.
A backend with nothing to open — console, file, in-memory — hands back itself, so the same code works in tests and in development without a branch.
Custom backend
Section titled “Custom backend”from buraq.contrib.email.backends.base import BaseEmailBackend
class MyBackend(BaseEmailBackend): def __init__(self, **kwargs): self.api_key = kwargs.get("API_KEY")
async def send(self, message) -> bool: return True # call your APIRegister in MAILERS:
MAILERS = { "custom": { "BACKEND": "myapp.backends.MyBackend", "API_KEY": "sk-...", },}Sending email
Section titled “Sending email”from buraq.contrib.email import send_mail
await send_mail( subject = "Welcome to Buraq Blog!", message = "Thanks for signing up.", from_email = "noreply@myblog.com", to = ["alice@example.com"],)HTML email
Section titled “HTML email”await send_mail( subject = "Your post was published", message = "Your post is now live.", # plain text fallback html_message = "<h1>Your post is live!</h1><p>Visit it at <a href='...'>here</a>.</p>", from_email = "noreply@myblog.com", to = ["alice@example.com"],)Multiple recipients
Section titled “Multiple recipients”await send_mail( subject = "Newsletter", message = "This month's updates...", to = ["alice@example.com", "bob@example.com"], cc = ["editor@myblog.com"], bcc = ["archive@myblog.com"],)Send mass mail
Section titled “Send mass mail”Send many emails efficiently in a single backend call.
from buraq.contrib.email import send_mass_mail
messages = [ ("Welcome!", "Thanks for joining.", ["alice@example.com"]), ("Welcome!", "Thanks for joining.", ["bob@example.com"]),]count = await send_mass_mail(messages) # → number of emails sentEmailMultiAlternatives — HTML + plain text
Section titled “EmailMultiAlternatives — HTML + plain text”EmailMultiAlternatives lets you attach multiple body formats (MIME multipart/alternative). Mail clients display the richest version they support:
from buraq.contrib.email.message import EmailMultiAlternatives
msg = EmailMultiAlternatives( subject = "Your order #1234 is confirmed", body = "Plain text body for older clients.", from_email= "orders@example.com", to = ["customer@example.com"],)msg.attach_alternative("<h1>Order confirmed</h1><p>Thank you!</p>", "text/html")await msg.send()send_template_mail
Section titled “send_template_mail”Render a Jinja2 template and send it as an email in one call:
from buraq.contrib.email import send_template_mail
await send_template_mail( template_name = "emails/welcome.html", context = {"user": user, "site_name": "My Blog"}, subject = "Welcome to My Blog!", to = [user.email],)The template renders to HTML. A plain-text fallback is derived automatically by stripping tags. Pass from_email= to override the default sender.
Email backends
Section titled “Email backends”| Backend class | Setting string | Description |
|---|---|---|
SMTPEmailBackend |
buraq.contrib.email.backends.smtp.SMTPEmailBackend |
Production SMTP delivery |
FileEmailBackend |
buraq.contrib.email.backends.file.FileEmailBackend |
Writes .eml files to disk (development) |
ConsoleEmailBackend |
buraq.contrib.email.backends.console.ConsoleEmailBackend |
Prints emails to stdout (development) |
DummyEmailBackend |
buraq.contrib.email.backends.dummy.DummyEmailBackend |
Silently discards every message |
EmailBackend (locmem) |
buraq.contrib.email.backends.locmem.EmailBackend |
Stores messages in a list; preferred for tests |
# Development — prints every email to the terminalEMAIL_BACKEND = "buraq.contrib.email.backends.console.ConsoleEmailBackend"
# CI — silently drops all emailEMAIL_BACKEND = "buraq.contrib.email.backends.dummy.DummyEmailBackend"In-memory email backend (tests)
Section titled “In-memory email backend (tests)”Use the locmem backend in tests so no email is actually delivered:
EMAIL_BACKEND = "buraq.contrib.email.backends.locmem.EmailBackend"from buraq.contrib.email.backends.locmem import outbox, clear_outboxfrom buraq.contrib.email import send_mail
# Clear state between testsclear_outbox()
await send_mail("Hi", "Hello!", ["to@example.com"])
assert len(outbox) == 1assert outbox[0].subject == "Hi"assert "Hello!" in outbox[0].bodyoutbox is a module-level list; all sent EmailMessage instances are appended to it. clear_outbox() empties it — call it in setUp / asyncSetUp.
Mail admins / managers
Section titled “Mail admins / managers”Quickly notify your site administrators or managers. Configure the recipients in settings:
ADMINS = [("Alice", "alice@example.com"), ("Bob", "bob@example.com")]MANAGERS = [("Carol", "carol@example.com")]from buraq.contrib.email import mail_admins, mail_managers
# Notify all ADMINSawait mail_admins("Server error", "Traceback:\n...")
# Notify all MANAGERSawait mail_managers("New signup", "User alice@example.com just registered.")
# With HTML bodyawait mail_admins("Alert", "Plain text.", html_message="<b>Alert</b>")