Environments
One of the core strengths of MailBridge is being able to use different email providers in different environments without changing a single line of application code. SMTP with Mailhog locally, SendGrid or Brevo in production — driven entirely by environment variables.
The Problem
Without a unified interface, switching providers between environments means touching application code or maintaining provider-specific branches. A typical project ends up with something like:
# Without MailBridge — provider-specific code everywhere
if settings.ENV == 'local':
import smtplib
# SMTP-specific setup...
elif settings.ENV == 'production':
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
# SendGrid-specific setup...
# Different call signatures, different response formats,
# different error handling — all duplicated per provider.
This creates friction: every provider switch requires code changes, and local behavior diverges from production.
The Pattern
With MailBridge, the provider is just configuration. Your application code never changes — only the environment variables do:
# mail.py — written once, never touched again
import os
from mailbridge import MailBridge
mailer = MailBridge(
provider=os.getenv('MAIL_PROVIDER', 'smtp'),
host=os.getenv('MAIL_HOST', 'localhost'),
port=int(os.getenv('MAIL_PORT', 1025)),
username=os.getenv('MAIL_USERNAME', ''),
password=os.getenv('MAIL_PASSWORD', ''),
api_key=os.getenv('MAIL_API_KEY', ''),
from_email=os.getenv('MAIL_FROM', 'noreply@yourdomain.com')
)
# Your application code — identical in every environment
from mail import mailer
mailer.send(
to=user.email,
subject='Welcome!',
template_id='welcome-email',
template_data={'name': user.name}
)
The only thing that differs between local, staging, and production is the .env file.
Local Development with Mailhog
Mailhog is a local SMTP server that catches all outgoing emails and displays them in a web UI — nothing ever reaches a real inbox. It's the recommended tool for local email development.
Start Mailhog
docker run -p 1025:1025 -p 8025:8025 mailhog/mailhog
SMTP listens on :1025, web UI at localhost:8025.
brew install mailhog
mailhog
# Download from https://github.com/mailhog/MailHog/releases
./MailHog
Configure MailBridge to use it
# .env.local
MAIL_PROVIDER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_FROM=dev@localhost
All emails sent by MailBridge will be caught by Mailhog and visible in the web UI — no real emails are sent, no API keys required.
Mailhog accepts any username/password — leave them empty. It does not require TLS either, so use_tls should be omitted or set to False.
Environment Files
A typical project has three .env files — one per environment. Only the values change, never the application code:
# .env.local — Mailhog, no credentials needed
MAIL_PROVIDER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_FROM=dev@localhost
# .env.staging — real provider, staging API key
MAIL_PROVIDER=sendgrid
MAIL_API_KEY=SG.staging_key_xxxxx
MAIL_FROM=staging@yourdomain.com
# .env.production — production API key and sender
MAIL_PROVIDER=sendgrid
MAIL_API_KEY=SG.production_key_xxxxx
MAIL_FROM=noreply@yourdomain.com
Load the right file at startup using python-dotenv or your framework's config system:
from dotenv import load_dotenv
import os
load_dotenv(f".env.{os.getenv('APP_ENV', 'local')}")
Django & Flask
# settings.py
from mailbridge import MailBridge
MAILER = MailBridge(
provider=env('MAIL_PROVIDER', default='smtp'),
host=env('MAIL_HOST', default='localhost'),
port=env.int('MAIL_PORT', default=1025),
username=env('MAIL_USERNAME', default=''),
password=env('MAIL_PASSWORD', default=''),
api_key=env('MAIL_API_KEY', default=''),
from_email=env('MAIL_FROM', default='noreply@yourdomain.com'),
)
# views.py or services.py
from django.conf import settings
settings.MAILER.send(
to=user.email,
subject='Welcome!',
template_id='welcome-email',
template_data={'name': user.name}
)
# app.py
import os
from flask import Flask
from mailbridge import MailBridge
app = Flask(__name__)
mailer = MailBridge(
provider=os.getenv('MAIL_PROVIDER', 'smtp'),
host=os.getenv('MAIL_HOST', 'localhost'),
port=int(os.getenv('MAIL_PORT', 1025)),
username=os.getenv('MAIL_USERNAME', ''),
password=os.getenv('MAIL_PASSWORD', ''),
api_key=os.getenv('MAIL_API_KEY', ''),
from_email=os.getenv('MAIL_FROM', 'noreply@yourdomain.com'),
)
# routes.py
from app import mailer
@app.route('/register', methods=['POST'])
def register():
user = create_user(request.json)
mailer.send(
to=user.email,
subject='Welcome!',
template_id='welcome-email',
template_data={'name': user.name}
)
return jsonify({"id": user.id})
Staging & Production
A common setup is to use the same provider in staging and production but with different API keys and sender addresses — so staging emails never reach real users:
# Environment summary
#
# Local → SMTP + Mailhog (no real emails, no API key)
# Staging → SendGrid (real provider, staging key, limited recipients)
# Production → SendGrid (real provider, production key, full send)
#
# Application code is identical across all three.
You can also switch providers entirely between environments — for example, use Brevo in staging because it has a generous free tier, and SendGrid in production for deliverability. MailBridge makes this a one-line config change:
# .env.staging
MAIL_PROVIDER=brevo
MAIL_API_KEY=xkeysib-staging-xxxxx
MAIL_FROM=staging@yourdomain.com
# .env.production
MAIL_PROVIDER=sendgrid
MAIL_API_KEY=SG.production_xxxxx
MAIL_FROM=noreply@yourdomain.com
Unused config keys are silently ignored by each provider — passing api_key to the SMTP provider or host to SendGrid causes no errors. This means you can keep all possible keys in a single config block and let the provider pick what it needs.