Pular para o conteúdo
couryo

Guia do desenvolvedorIntegrações

Enviar e-mail no Django

Enviar e-mail pelo Couryo no Django com send_mail e EmailMultiAlternatives, pelo backend do SDK Python ou por SMTP, e receber webhooks assinados.

Ver em Markdown

O send_mail, o EmailMessage e os e-mails do próprio Django (recuperação de senha do django.contrib.auth, avisos de erro) passam a sair pelo Couryo trocando só o backend.

Opção 1: backend do SDK#

Terminal
pip install "couryo[django]"

No Django 6.1 ou mais novo, o backend vai em MAILERS:

settings.py
import os

MAILERS = {
    "default": {
        "BACKEND": "couryo.django.EmailBackend",
        "OPTIONS": {"api_key": os.environ["COURYO_API_KEY"]},  # ck_test_... enquanto testa
    },
}
DEFAULT_FROM_EMAIL = "Exemplo <oi@exemplo.com.br>"
SERVER_EMAIL = "alertas@exemplo.com.br"

No Django 6.0 ou mais antigo, use EMAIL_BACKEND (o Django 6.1 ainda aceita, com aviso de descontinuação, mas não junto com MAILERS):

settings.py
import os

EMAIL_BACKEND = "couryo.django.EmailBackend"
COURYO_API_KEY = os.environ["COURYO_API_KEY"]  # ck_test_... enquanto testa
DEFAULT_FROM_EMAIL = "Exemplo <oi@exemplo.com.br>"
SERVER_EMAIL = "alertas@exemplo.com.br"

O código que já existe não muda:

pedidos/emails.py
from django.core.mail import EmailMultiAlternatives, send_mail
from django.template.loader import render_to_string

send_mail("Pedido recebido", "Obrigado pela compra!", None, ["ana@exemplo.com.br"])

contexto = {"pedido": pedido}
msg = EmailMultiAlternatives(
    subject=f"Pedido {pedido.numero} enviado",
    body=render_to_string("emails/pedido_enviado.txt", contexto),
    to=[pedido.cliente.email],
    headers={"X-Couryo-Tags": "tipo=pedido"},
)
msg.attach_alternative(render_to_string("emails/pedido_enviado.html", contexto), "text/html")
msg.send()

Anexos (msg.attach(...)) e reply_to, cc e bcc seguem para a API. O cabeçalho X-Couryo-Stream: marketing manda no fluxo de marketing; sem ele, o e-mail é transacional.

Chamar a API direto#

contas/views.py
from couryo import Couryo

couryo = Couryo()  # lê COURYO_API_KEY

couryo.events.track(
    {"contact": {"email": usuario.email, "attributes": {"nome": usuario.first_name}}, "name": "user.signed_up"},
    idempotency_key=f"signup-{usuario.pk}",
)

Em views assíncronas, use AsyncCouryo com await.

Opção 2: SMTP#

No Django 6.1 ou mais novo:

settings.py
MAILERS = {
    "default": {
        "BACKEND": "django.core.mail.backends.smtp.EmailBackend",
        "OPTIONS": {
            "host": "smtp.couryo.com",
            "port": 587,
            "use_tls": True,  # STARTTLS; para a porta 465, use "use_ssl": True no lugar
            "username": "couryo",
            "password": os.environ["COURYO_API_KEY"],
        },
    },
}
DEFAULT_FROM_EMAIL = "Exemplo <oi@exemplo.com.br>"

No Django 6.0 ou mais antigo:

settings.py
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.couryo.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True  # STARTTLS; para a porta 465, use EMAIL_USE_SSL = True
EMAIL_HOST_USER = "couryo"
EMAIL_HOST_PASSWORD = os.environ["COURYO_API_KEY"]
DEFAULT_FROM_EMAIL = "Exemplo <oi@exemplo.com.br>"

use_tls e use_ssl (EMAIL_USE_TLS e EMAIL_USE_SSL) não podem ser True ao mesmo tempo. Detalhes e códigos de resposta em SMTP.

Receber webhooks#

webhooks/views.py
import os

from couryo import WebhookVerificationError, verify_webhook
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST


@csrf_exempt
@require_POST
def couryo_webhook(request):
    try:
        evento = verify_webhook(os.environ["COURYO_WEBHOOK_SECRET"], request.headers, request.body)
    except WebhookVerificationError:
        return HttpResponseBadRequest("assinatura inválida")

    if evento["type"] == "bounced":
        ...  # evento["data"]["recipient"], evento["data"]["explanation"]
    return HttpResponse(status=204)

request.body é o corpo cru, exatamente como a assinatura espera. A view fica fora do CSRF porque a assinatura já protege.

Recuperação de senha do Django#

A PasswordResetView usa o mailer padrão (MAILERS["default"], ou o EMAIL_BACKEND no Django 6.0 e anteriores), então já sai pelo Couryo. Confira só o DEFAULT_FROM_EMAIL (num domínio verificado) e o domínio do link: em produção, o Django monta o link com o host da requisição, então mantenha o ALLOWED_HOSTS certo. Se o e-mail de senha não chega, o guia Recuperação de senha não chega passa pelas causas uma a uma.

Problemas comuns#

Sintoma O que fazer
InvalidRequestError com code domain_not_verified o DEFAULT_FROM_EMAIL precisa estar num domínio verificado no projeto
Nada acontece e nenhum erro fail_silently=True esconde erros; tire para ver a mensagem do Couryo
SMTPAuthenticationError (535) usuário diferente de couryo ou chave inválida, revogada ou sem escopo send