Tutoriel
Envoyer des e-mails avec Python
Envoyez des e-mails depuis Python avec le SDK emailit, le backend SMTP de Django ou smtplib, et vérifiez les webhooks Emailit dans Flask ou Django.
Ce guide montre comment envoyer des e-mails depuis Python avec le paquet officiel emailit, comment utiliser plutôt le SMTP depuis Django ou la bibliothèque standard, et comment vérifier les signatures des webhooks dans Flask et Django.
Prérequis
- Python 3.7 ou version ultérieure.
- Un domaine d’envoi vérifié, par exemple
acme.com. - Une clé API. Une clé Sending Only limitée à votre domaine suffit.
- Tant que votre espace de travail n’a pas l’accès production, vous ne pouvez envoyer qu’aux adresses e-mail des comptes des membres de l’espace de travail.
Installer le SDK
pip install emailitLe SDK s’appuie sur requests.
Configurer votre clé API
Définissez la clé dans l’environnement de votre serveur d’application, de votre worker ou de votre conteneur :
export EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••Lisez-la avec os.environ["EMAILIT_API_KEY"] : une clé absente provoque ainsi une erreur visible dès le démarrage plutôt qu’au premier envoi.
Envoyer un e-mail
import os
from emailit import EmailitClient
client = EmailitClient(os.environ["EMAILIT_API_KEY"])
email = client.emails.send({
"from": "Acme <hello@acme.com>",
"to": "ada@example.com",
"subject": "Reset your password",
"html": "<p>Use this link to reset your password.</p>",
"text": "Use this link to reset your password.",
})
print(email.id) # em_…Créez le client une seule fois et réutilisez-le. Le même appel send accepte cc, bcc, reply_to, attachments, template avec variables, scheduled_at et tracking ; consultez Envoyer un e-mail.
Gérer les erreurs
from emailit import (
ApiErrorException,
AuthenticationException,
RateLimitException,
UnprocessableEntityException,
)
try:
client.emails.send(message)
except RateLimitException:
# 429: wait and retry, for example from a task queue
raise
except AuthenticationException:
# 401: the API key is missing or invalid
raise
except UnprocessableEntityException as e:
# 422: for example, the from domain isn't verified
print(e.json_body)
except ApiErrorException as e:
print(e.http_status, e.json_body)Le SDK est synchrone. Dans les frameworks asynchrones comme FastAPI, appelez-le depuis un endpoint def classique ou une tâche en arrière-plan pour ne pas bloquer la boucle d’événements.
Envoyer plutôt via SMTP
Django
Le backend SMTP intégré de Django fonctionne avec Emailit. Ajoutez ceci à settings.py :
import os
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.emailit.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "emailit"
EMAIL_HOST_PASSWORD = os.environ["EMAILIT_API_KEY"]
DEFAULT_FROM_EMAIL = "Acme <hello@acme.com>"
SERVER_EMAIL = "alerts@acme.com"Utilisez ensuite les fonctions d’envoi de Django comme d’habitude :
from django.core.mail import send_mail
send_mail(
subject="Reset your password",
message="Use this link to reset your password.",
from_email=None, # uses DEFAULT_FROM_EMAIL
recipient_list=["ada@example.com"],
html_message="<p>Use this link to reset your password.</p>",
)DEFAULT_FROM_EMAIL et SERVER_EMAIL doivent utiliser un domaine d’envoi vérifié. Pour le TLS implicite sur le port 465, définissez EMAIL_PORT = 465 et EMAIL_USE_SSL = True au lieu de EMAIL_USE_TLS.
Bibliothèque standard
Sans framework, utilisez smtplib :
import os
import smtplib
from email.message import EmailMessage
message = EmailMessage()
message["From"] = "Acme <hello@acme.com>"
message["To"] = "ada@example.com"
message["Subject"] = "Reset your password"
message.set_content("Use this link to reset your password.")
message.add_alternative("<p>Use this link to reset your password.</p>", subtype="html")
with smtplib.SMTP("smtp.emailit.com", 587) as smtp:
smtp.starttls()
smtp.login("emailit", os.environ["EMAILIT_API_KEY"])
smtp.send_message(message)Si votre réseau bloque le port 587, utilisez le port 2525 ou 2587 avec le même code. Consultez Paramètres SMTP.
Recevoir des webhooks
Créez un webhook et enregistrez son secret de signature dans EMAILIT_WEBHOOK_SECRET. Vérifiez la signature à partir du corps brut de la requête avec cette fonction utilitaire :
import hashlib
import hmac
import os
import time
def is_valid_signature(raw_body: bytes, signature: str, timestamp: str) -> bool:
if not signature or not timestamp or not timestamp.isdigit():
return False
if abs(time.time() - int(timestamp)) > 300:
return False
secret = os.environ["EMAILIT_WEBHOOK_SECRET"].encode()
expected = hmac.new(secret, timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)Appelez-la ensuite depuis la route de votre framework :
import json
from flask import Flask, abort, request
from emailit_webhooks import is_valid_signature
app = Flask(__name__)
@app.post("/webhooks/emailit")
def emailit_webhook():
raw_body = request.get_data()
if not is_valid_signature(
raw_body,
request.headers.get("X-Emailit-Signature", ""),
request.headers.get("X-Emailit-Timestamp", ""),
):
abort(401)
for event in json.loads(raw_body): # an array of up to 100 events
if event["type"] == "email.bounced":
address = event["data"]["object"]["to"]
# Stop emailing this address.
return "", 200import json
from django.http import HttpResponse, HttpResponseForbidden
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from emailit_webhooks import is_valid_signature
@csrf_exempt
@require_POST
def emailit_webhook(request):
if not is_valid_signature(
request.body,
request.headers.get("X-Emailit-Signature", ""),
request.headers.get("X-Emailit-Timestamp", ""),
):
return HttpResponseForbidden("Invalid signature")
for event in json.loads(request.body): # an array of up to 100 events
if event["type"] == "email.complained":
address = event["data"]["object"]["to"]
# Stop emailing this address.
return HttpResponse(status=200)Renvoyez un 2xx dans les 30 secondes ; pour toute autre réponse, Emailit réessaie. Consultez Signature des requêtes.
Conseils pour la production
- Envoyez depuis une file de tâches. Utilisez Celery, RQ ou les tâches en arrière-plan de votre framework pour les envois en masse, et limitez le débit des workers pour rester sous vos limites d’envoi (2 e-mails par seconde par défaut).
- Sécurisez les relances. Quand une tâche est relancée après un timeout, envoyez un en-tête
Idempotency-Key(avecrequestsouhttpx) pour que l’e-mail ne soit pas envoyé deux fois. Consultez Idempotence. - Dédoublonnez les webhooks. Enregistrez chaque
event_idtraité : Emailit réessaie les livraisons en échec, qui peuvent donc arriver plusieurs fois. - Utilisez une clé par environnement et chargez-la depuis l’environnement ou un gestionnaire de secrets. Ne l’écrivez jamais en dur dans
settings.py.