Python

Envio de E-mails e Integração com APIs de Comunicação Já leu

13 min de leitura

Envio de E-mails e Integração com APIs de Comunicação
Comunicação programática é um pilar de qualquer sistema moderno — confirmações de cadastro, alertas de sistema, notificações de pagamento, relatórios automáticos, mensagens de suporte. Python tem suporte nativo a SMTP pa

Comunicação programática é um pilar de qualquer sistema moderno — confirmações de cadastro, alertas de sistema, notificações de pagamento, relatórios automáticos, mensagens de suporte. Python tem suporte nativo a SMTP para e-mails e integração direta com as principais APIs de comunicação do mercado: SendGrid, Twilio, Slack e WhatsApp. Neste artigo veremos como implementar cada uma dessas integrações de forma profissional.


E-mail com smtplib: Protocolo Nativo

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email import encoders
from pathlib import Path
import os


class ClienteEmail:
    """Cliente SMTP reutilizável com suporte a TLS."""

    def __init__(
        self,
        host:  str,
        porta: int,
        usuario: str,
        senha:   str,
        tls:     bool = True
    ):
        self.host    = host
        self.porta   = porta
        self.usuario = usuario
        self.senha   = senha
        self.tls     = tls

    def _criar_conexao(self) -> smtplib.SMTP:
        smtp = smtplib.SMTP(self.host, self.porta)
        smtp.ehlo()
        if self.tls:
            smtp.starttls()
            smtp.ehlo()
        smtp.login(self.usuario, self.senha)
        return smtp

    def enviar(
        self,
        destinatarios: list,
        assunto:       str,
        corpo_texto:   str,
        corpo_html:    str = None,
        anexos:        list = None,
        cc:            list = None,
        bcc:           list = None,
        remetente_nome: str = None
    ) -> bool:
        msg = MIMEMultipart("alternative")

        # Cabeçalhos
        remetente = (
            f"{remetente_nome} <{self.usuario}>"
            if remetente_nome else self.usuario
        )
        msg["From"]    = remetente
        msg["To"]      = ", ".join(destinatarios)
        msg["Subject"] = assunto
        if cc:
            msg["Cc"] = ", ".join(cc)

        # Corpo texto puro (fallback)
        msg.attach(MIMEText(corpo_texto, "plain", "utf-8"))

        # Corpo HTML (preferido pelos clientes de e-mail)
        if corpo_html:
            msg.attach(MIMEText(corpo_html, "html", "utf-8"))

        # Anexos
        if anexos:
            for caminho_anexo in anexos:
                arquivo = Path(caminho_anexo)
                if not arquivo.exists():
                    continue
                with open(arquivo, "rb") as f:
                    parte = MIMEBase("application", "octet-stream")
                    parte.set_payload(f.read())
                encoders.encode_base64(parte)
                parte.add_header(
                    "Content-Disposition",
                    f"attachment; filename={arquivo.name}"
                )
                msg.attach(parte)

        # Todos os destinatários (To + CC + BCC)
        todos = destinatarios + (cc or []) + (bcc or [])

        try:
            with self._criar_conexao() as smtp:
                smtp.sendmail(self.usuario, todos, msg.as_string())
            print(f"E-mail enviado para {len(todos)} destinatário(s).")
            return True
        except smtplib.SMTPException as e:
            print(f"Erro ao enviar e-mail: {e}")
            return False


# Configurações por provedor
def gmail(usuario, senha):
    return ClienteEmail("smtp.gmail.com", 587, usuario, senha)

def outlook(usuario, senha):
    return ClienteEmail("smtp.office365.com", 587, usuario, senha)

def yahoo(usuario, senha):
    return ClienteEmail("smtp.mail.yahoo.com", 587, usuario, senha)


# Uso
cliente = gmail(
    os.getenv("EMAIL_USER"),
    os.getenv("EMAIL_PASS")   # use App Password no Gmail
)

cliente.enviar(
    destinatarios=["ana@email.com"],
    assunto="Boletim Escolar — Março 2024",
    corpo_texto="Segue em anexo seu boletim escolar.",
    corpo_html="""
        <h2>Olá, Ana!</h2>
        <p>Segue em anexo seu <strong>boletim escolar</strong> de Março 2024.</p>
        <p>Em caso de dúvidas, entre em contato com a secretaria.</p>
    """,
    anexos=["boletim_2024-03.pdf"],
    remetente_nome="Sistema Escolar"
)

Templates de E-mail com Jinja2

from jinja2 import Environment, FileSystemLoader
from pathlib import Path


def criar_ambiente_templates():
    Path("templates/email").mkdir(parents=True, exist_ok=True)
    return Environment(
        loader=FileSystemLoader("templates/email"),
        autoescape=True
    )


# templates/email/base.html
TEMPLATE_BASE = """
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <style>
    body        { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; }
    .container  { max-width: 600px; margin: 20px auto; background: white;
                  border-radius: 8px; overflow: hidden; }
    .header     { background: #1F4E79; color: white; padding: 30px;
                  text-align: center; }
    .header h1  { margin: 0; font-size: 22px; }
    .body       { padding: 30px; color: #333; line-height: 1.6; }
    .footer     { background: #f4f4f4; padding: 15px; text-align: center;
                  font-size: 12px; color: #888; }
    .btn        { display: inline-block; background: #2E75B6; color: white;
                  padding: 12px 24px; border-radius: 4px; text-decoration: none;
                  font-weight: bold; margin: 15px 0; }
    .alerta     { background: #FCE4D6; border-left: 4px solid #C00000;
                  padding: 12px; border-radius: 4px; }
    .sucesso    { background: #E2EFDA; border-left: 4px solid #375623;
                  padding: 12px; border-radius: 4px; }
    table       { width: 100%; border-collapse: collapse; }
    th          { background: #2E75B6; color: white; padding: 10px; }
    td          { padding: 8px; border-bottom: 1px solid #eee; }
    tr:nth-child(even) td { background: #f9f9f9; }
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <h1>{% block titulo %}Sistema Escolar{% endblock %}</h1>
    </div>
    <div class="body">
      {% block conteudo %}{% endblock %}
    </div>
    <div class="footer">
      <p>© 2024 Sistema Escolar. Este é um e-mail automático.</p>
      <p><a href="{{ url_cancelar }}">Cancelar inscrição</a></p>
    </div>
  </div>
</body>
</html>
"""

# templates/email/boas_vindas.html
TEMPLATE_BOAS_VINDAS = """
{% extends "base.html" %}

{% block titulo %}Bem-vindo(a), {{ nome }}!{% endblock %}

{% block conteudo %}
<p>Olá, <strong>{{ nome }}</strong>!</p>
<p>Sua conta foi criada com sucesso no <strong>Sistema Escolar</strong>.</p>

<div class="sucesso">
  <strong>Seus dados de acesso:</strong><br>
  E-mail: {{ email }}<br>
  Senha temporária: {{ senha_temp }}
</div>

<p>Por segurança, recomendamos alterar sua senha no primeiro acesso.</p>

<a href="{{ url_acesso }}" class="btn">Acessar o Sistema</a>

<p>Em caso de problemas, entre em contato com a secretaria.</p>
{% endblock %}
"""


class ServicoEmail:
    def __init__(self, cliente: ClienteEmail):
        self.cliente = cliente
        self.env     = criar_ambiente_templates()

        # Salva templates em disco
        Path("templates/email/base.html").write_text(TEMPLATE_BASE)
        Path("templates/email/boas_vindas.html").write_text(TEMPLATE_BOAS_VINDAS)

    def _renderizar(self, template_nome: str, contexto: dict) -> str:
        template = self.env.get_template(template_nome)
        return template.render(**contexto, url_cancelar="#")

    def boas_vindas(self, nome: str, email: str, senha_temp: str):
        html = self._renderizar("boas_vindas.html", {
            "nome":       nome,
            "email":      email,
            "senha_temp": senha_temp,
            "url_acesso": "https://sistema.escolar.com/login"
        })
        texto = f"Olá, {nome}! Sua conta foi criada. Senha temporária: {senha_temp}"

        return self.cliente.enviar(
            destinatarios=[email],
            assunto=f"Bem-vindo(a) ao Sistema Escolar, {nome}!",
            corpo_texto=texto,
            corpo_html=html,
            remetente_nome="Sistema Escolar"
        )

    def boletim(self, aluno: dict, pdf_path: str):
        html = f"""
        <h2>Olá, {aluno['nome']}!</h2>
        <p>Seu boletim do mês está disponível em anexo.</p>
        <table>
          <tr><th>Disciplina</th><th>Nota</th><th>Status</th></tr>
          {''.join(
              f"<tr><td>{d['nome']}</td><td>{d['nota']}</td>"
              f"<td>{'✓' if d['nota'] >= 6 else '✗'}</td></tr>"
              for d in aluno.get('disciplinas', [])
          )}
        </table>
        """
        return self.cliente.enviar(
            destinatarios=[aluno["email"]],
            assunto="Seu Boletim Escolar está disponível",
            corpo_texto=f"Olá {aluno['nome']}, seu boletim está em anexo.",
            corpo_html=html,
            anexos=[pdf_path]
        )

SendGrid: API Profissional de E-mail

pip install sendgrid
import os
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import (
    Mail, To, From, Subject,
    PlainTextContent, HtmlContent,
    Attachment, FileContent, FileName,
    FileType, Disposition
)
import base64


class ClienteSendGrid:
    def __init__(self, api_key: str, remetente: str, nome_remetente: str = ""):
        self.sg         = SendGridAPIClient(api_key)
        self.remetente  = remetente
        self.nome       = nome_remetente

    def enviar(
        self,
        destinatario: str,
        assunto:      str,
        html:         str,
        texto:        str = None,
        anexos:       list = None
    ) -> bool:
        mensagem = Mail(
            from_email=From(self.remetente, self.nome),
            to_emails=To(destinatario),
            subject=Subject(assunto),
            plain_text_content=PlainTextContent(texto or ""),
            html_content=HtmlContent(html)
        )

        if anexos:
            for caminho in anexos:
                arquivo = Path(caminho)
                with open(arquivo, "rb") as f:
                    dados = base64.b64encode(f.read()).decode()

                anexo = Attachment(
                    file_content=FileContent(dados),
                    file_name=FileName(arquivo.name),
                    file_type=FileType("application/octet-stream"),
                    disposition=Disposition("attachment")
                )
                mensagem.attachment = anexo

        try:
            resposta = self.sg.send(mensagem)
            print(f"SendGrid: {resposta.status_code}")
            return resposta.status_code in [200, 202]
        except Exception as e:
            print(f"Erro SendGrid: {e}")
            return False


sg = ClienteSendGrid(
    api_key=os.getenv("SENDGRID_API_KEY"),
    remetente="noreply@escola.com",
    nome_remetente="Sistema Escolar"
)

sg.enviar(
    destinatario="aluno@email.com",
    assunto="Confirmação de Matrícula",
    html="<h1>Matrícula confirmada!</h1><p>Bem-vindo(a).</p>",
    texto="Matrícula confirmada! Bem-vindo(a)."
)

Twilio: SMS e WhatsApp

pip install twilio
import os
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException


class ClienteTwilio:
    def __init__(self, account_sid: str, auth_token: str, numero: str):
        self.client = Client(account_sid, auth_token)
        self.numero = numero   # número Twilio: +15551234567

    def enviar_sms(self, destinatario: str, mensagem: str) -> bool:
        """Envia SMS. destinatario no formato +5511999999999"""
        try:
            msg = self.client.messages.create(
                body=mensagem,
                from_=self.numero,
                to=destinatario
            )
            print(f"SMS enviado: {msg.sid}")
            return True
        except TwilioRestException as e:
            print(f"Erro Twilio: {e}")
            return False

    def enviar_whatsapp(self, destinatario: str, mensagem: str) -> bool:
        """
        Envia WhatsApp via Twilio Sandbox.
        destinatario: whatsapp:+5511999999999
        """
        try:
            msg = self.client.messages.create(
                body=mensagem,
                from_=f"whatsapp:{self.numero}",
                to=f"whatsapp:{destinatario}"
            )
            print(f"WhatsApp enviado: {msg.sid}")
            return True
        except TwilioRestException as e:
            print(f"Erro WhatsApp: {e}")
            return False

    def enviar_whatsapp_template(
        self,
        destinatario: str,
        template_sid: str,
        variaveis:    dict
    ) -> bool:
        """Envia mensagem usando template aprovado pelo WhatsApp."""
        try:
            msg = self.client.messages.create(
                from_=f"whatsapp:{self.numero}",
                to=f"whatsapp:{destinatario}",
                content_sid=template_sid,
                content_variables=str(variaveis)
            )
            return True
        except TwilioRestException as e:
            print(f"Erro template: {e}")
            return False


twilio = ClienteTwilio(
    account_sid=os.getenv("TWILIO_ACCOUNT_SID"),
    auth_token= os.getenv("TWILIO_AUTH_TOKEN"),
    numero=     os.getenv("TWILIO_NUMBER")
)

# SMS
twilio.enviar_sms(
    "+5511999999999",
    "Sua nota foi lançada: 8.5. Acesse o portal para ver o boletim."
)

# WhatsApp
twilio.enviar_whatsapp(
    "+5511999999999",
    "🎓 *Sistema Escolar*\nOlá, Ana! Sua nota de Matemática foi lançada: *8.5*"
)

Slack: Notificações para Equipes

pip install slack-sdk
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError


class ClienteSlack:
    def __init__(self, token: str):
        self.client = WebClient(token=token)

    def enviar_mensagem(self, canal: str, texto: str) -> bool:
        try:
            self.client.chat_postMessage(channel=canal, text=texto)
            return True
        except SlackApiError as e:
            print(f"Erro Slack: {e.response['error']}")
            return False

    def enviar_bloco(self, canal: str, blocos: list) -> bool:
        """Envia mensagem com Block Kit — layout rico."""
        try:
            self.client.chat_postMessage(channel=canal, blocks=blocos)
            return True
        except SlackApiError as e:
            print(f"Erro Slack: {e.response['error']}")
            return False

    def notificar_alerta(self, canal: str, titulo: str, mensagem: str, urgente: bool = False):
        cor   = "#C00000" if urgente else "#2E75B6"
        emoji = "🚨" if urgente else "ℹ️"

        blocos = [
            {
                "type": "header",
                "text": {"type": "plain_text", "text": f"{emoji} {titulo}"}
            },
            {
                "type": "section",
                "text": {"type": "mrkdwn", "text": mensagem}
            },
            {
                "type": "context",
                "elements": [{
                    "type": "mrkdwn",
                    "text": f"*Sistema Escolar* | {datetime.now().strftime('%d/%m/%Y %H:%M')}"
                }]
            }
        ]
        return self.enviar_bloco(canal, blocos)

    def notificar_boletim(self, canal: str, aluno: str, nota: float):
        status = "✅ Aprovado" if nota >= 6 else "❌ Reprovado"
        blocos = [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"*Nova nota lançada*\n*Aluno:* {aluno}\n*Nota:* {nota:.1f}\n*Status:* {status}"
                }
            },
            {"type": "divider"},
            {
                "type": "actions",
                "elements": [{
                    "type": "button",
                    "text": {"type": "plain_text", "text": "Ver Boletim"},
                    "url":  "https://sistema.escolar.com/boletim"
                }]
            }
        ]
        return self.enviar_bloco(canal, blocos)


from datetime import datetime

slack = ClienteSlack(token=os.getenv("SLACK_BOT_TOKEN"))

slack.notificar_alerta(
    canal="#alertas-sistema",
    titulo="Erro no processamento",
    mensagem="Falha ao gerar boletins da turma B. Verificar logs.",
    urgente=True
)

slack.notificar_boletim(
    canal="#notas",
    aluno="Ana Silva",
    nota=9.5
)

Exemplo Completo: Sistema Centralizado de Notificações

from enum import Enum
from dataclasses import dataclass
from typing import Optional
import os


class Canal(str, Enum):
    EMAIL    = "email"
    SMS      = "sms"
    WHATSAPP = "whatsapp"
    SLACK    = "slack"


@dataclass
class Notificacao:
    destinatario: str
    assunto:      str
    mensagem:     str
    mensagem_html: Optional[str] = None
    canal:        Canal = Canal.EMAIL
    urgente:      bool  = False
    anexos:       list  = None


class SistemaNotificacoes:
    def __init__(self):
        self._email   = ClienteEmail(
            host=os.getenv("SMTP_HOST", "smtp.gmail.com"),
            porta=587,
            usuario=os.getenv("EMAIL_USER"),
            senha=os.getenv("EMAIL_PASS")
        )
        self._twilio  = ClienteTwilio(
            os.getenv("TWILIO_ACCOUNT_SID"),
            os.getenv("TWILIO_AUTH_TOKEN"),
            os.getenv("TWILIO_NUMBER")
        )
        self._slack   = ClienteSlack(os.getenv("SLACK_BOT_TOKEN"))

    def enviar(self, notif: Notificacao) -> bool:
        print(f"[{notif.canal.value.upper()}] → {notif.destinatario}: {notif.assunto}")

        if notif.canal == Canal.EMAIL:
            return self._email.enviar(
                destinatarios=[notif.destinatario],
                assunto=notif.assunto,
                corpo_texto=notif.mensagem,
                corpo_html=notif.mensagem_html,
                anexos=notif.anexos or []
            )
        elif notif.canal == Canal.SMS:
            return self._twilio.enviar_sms(notif.destinatario, notif.mensagem)

        elif notif.canal == Canal.WHATSAPP:
            return self._twilio.enviar_whatsapp(notif.destinatario, notif.mensagem)

        elif notif.canal == Canal.SLACK:
            return self._slack.notificar_alerta(
                canal=notif.destinatario,
                titulo=notif.assunto,
                mensagem=notif.mensagem,
                urgente=notif.urgente
            )
        return False

    def enviar_multiplos(self, notificacoes: list) -> dict:
        resultados = {"sucesso": 0, "falha": 0}
        for notif in notificacoes:
            if self.enviar(notif):
                resultados["sucesso"] += 1
            else:
                resultados["falha"] += 1
        return resultados


# Uso
sistema = SistemaNotificacoes()

# Notificar resultado de prova por múltiplos canais
aluno = {
    "nome":      "Ana Silva",
    "email":     "ana@email.com",
    "telefone":  "+5511999999999",
    "nota":      9.5
}

notificacoes = [
    Notificacao(
        destinatario=aluno["email"],
        assunto="Sua nota foi lançada!",
        mensagem=f"Olá {aluno['nome']}, sua nota é {aluno['nota']}.",
        mensagem_html=f"<h2>Olá, {aluno['nome']}!</h2><p>Nota: <strong>{aluno['nota']}</strong></p>",
        canal=Canal.EMAIL
    ),
    Notificacao(
        destinatario=aluno["telefone"],
        assunto="Nota lançada",
        mensagem=f"Olá {aluno['nome']}! Sua nota: {aluno['nota']}. Aprovado(a)! 🎉",
        canal=Canal.SMS
    ),
    Notificacao(
        destinatario="#professores",
        assunto="Nota lançada",
        mensagem=f"*{aluno['nome']}* recebeu nota *{aluno['nota']}*",
        canal=Canal.SLACK
    ),
]

resultado = sistema.enviar_multiplos(notificacoes)
print(f"\nResultado: {resultado['sucesso']} enviados, {resultado['falha']} falhas")

Resumo

  • smtplib envia e-mails via SMTP com suporte a TLS, anexos, HTML e múltiplos destinatários
  • Templates Jinja2 separam conteúdo de código — essencial para e-mails com layout profissional
  • SendGrid é a escolha para envios em escala — alta entregabilidade e métricas detalhadas
  • Twilio integra SMS e WhatsApp com uma única API — ideal para notificações críticas
  • Slack SDK permite notificações ricas com Block Kit — botões, seções e contexto formatado
  • Um sistema centralizado de notificações abstrai os canais — adicionar novos não quebra o código existente
  • Sempre use variáveis de ambiente para credenciais de APIs — nunca coloque tokens no código

Referências e Leituras Complementares

  • smtplib — documentação oficial — https://docs.python.org/3/library/smtplib.html
  • email.mime — documentação oficial — https://docs.python.org/3/library/email.mime.html
  • SendGrid Python SDK — https://github.com/sendgrid/sendgrid-python
  • Twilio Python SDK — https://www.twilio.com/docs/libraries/python
  • Slack SDK para Python — https://slack.dev/python-slack-sdk/
  • Slack Block Kit Builder — https://app.slack.com/block-kit-builder
  • SWEIGART, Al. Automate the Boring Stuff with Python. 2. ed. No Starch Press, 2019. Cap. 16 — envio de e-mails e SMS com Python.
  • PERCIVAL, Harry; GREGORY, Bob. Architecture Patterns with Python. O'Reilly Media, 2020. Cap. 8 — adaptadores para serviços externos e testes de integração.
Comentários

Mais em Python

Leitura e Escrita de Arquivos
Leitura e Escrita de Arquivos

Programas raramente existem de forma isolada — eles leem configurações, proce...

Decoradores e Metaprogramação
Decoradores e Metaprogramação

Decoradores s&atilde;o um dos recursos mais elegantes do Python. Eles permite...

Artigo 50 — Projeto 3: Bot de Automação com Scraping e Notificações
Artigo 50 — Projeto 3: Bot de Automação com Scraping e Notificações

O Projeto 3 é um bot de automação completo — monitora fontes de dados na web,...