Documentação API

Referência completa para integrar qualquer sistema interno da B20 à B20Mail. A API é REST, aceita JSON e autentica por API Key via header.

VISÃO GERAL

A B20Mail é o único ponto de acesso ao Amazon SES na B20. Nenhum sistema deve configurar credenciais AWS diretamente — todos os envios passam por esta API.

Endpoint de Produção Oficial: https://api.b20mail.com
ItemValor
ProtocoloHTTPS (HTTP aceito em dev local)
FormatoJSON (Content-Type: application/json)
AutenticaçãoAPI Key via header x-api-key
ProviderAmazon SES via AWS SDK PHP
LogsMySQL + arquivo mensal em logs/

AUTENTICAÇÃO

Toda requisição às rotas protegidas deve incluir o header x-api-key com a chave do projeto. Sua chave fica no painel, na aba API Keys — é uma sequência de 64 caracteres hexadecimais.

# Header obrigatório em todas as requisições autenticadas
x-api-key: <cole aqui a chave de 64 caracteres do seu projeto>
Segurança da sua API Key — leia antes de integrar. A chave dá poder total de envio em nome dos seus domínios verificados. Trate-a como uma senha de produção:
  • Somente no servidor (server-to-server). Use a chave no backend do seu sistema. Nunca em JavaScript de navegador, app mobile ou qualquer código que chegue ao usuário final — a API recusa chamadas de navegador (CORS) justamente para impedir esse tipo de vazamento.
  • Sempre via variável de ambiente. Carregue a chave de .env / variável de ambiente. Nunca escreva a chave direto no código-fonte nem faça commit dela em repositório (mesmo privado).
  • Nunca em logs, prints ou tickets. Ao pedir suporte ou compartilhar uma tela, oculte a chave.
  • Rotacione se vazar. Se suspeitar de exposição, desative o projeto no painel e gere uma nova chave — a antiga deixa de funcionar imediatamente.

RATE LIMITING

Cada API Key possui um limite de requisições por minuto configurado no .env (padrão: 60 req/min). Ao exceder, a API retorna 429 Too Many Requests.

Variável .envPadrãoDescrição
RATE_LIMIT_PER_MINUTE60Máximo de requisições por minuto por chave

FORMATO DE RESPOSTA

Todas as respostas seguem o mesmo envelope JSON:

{
  "success":    true | false,
  "message":    "Mensagem descritiva",
  "request_id": "uuid-para-rastreio",
  // campos extras dependendo da rota
}

ENDPOINTS

GETPÚBLICO/ping
Verifica a saúde da API e a latência do servidor. Não requer autenticação. Use para verificar se o serviço está online.
# Resposta 200
{
  "success":    true,
  "message":    "pong",
  "version":    "1.0.0",
  "env":        "local",
  "time":       "2026-05-18 16:00:00"
}
POST/send-email
Envia um email usando um template pré-definido. Requer API Key válida.
⚠️ REMETENTE OBRIGATÓRIO E AUTORIZADO: A B20Mail opera como uma infraestrutura pura de envios White-Label. Isso significa que não assumimos a autoria dos seus disparos. Você é obrigado a assinar seus e-mails preenchendo a chave from no formato Nome <[email protected]>. Além disso, o seu domínio precisa estar obrigatoriamente cadastrado e verificado na aba "REMETENTES" do seu painel.

Body da Requisição

ParâmetroTipoObrig.Descrição
tostringSimEmail do destinatário.
fromstringSimRemetente ("Nome <[email protected]>"). Só funciona se o domínio foi cadastrado e validado no seu painel.
subjectstringSimAssunto do email (max 200 chars)
templatestringSim*Nome do template: welcome, recover-password, notification.
*Obrigatório se não enviar html
htmlstringNãoEnvie seu próprio HTML puro aqui. A API aceita o HTML completo e ainda injeta suas variáveis nele. Sobrescreve o template.
typestringNãotransactional ou marketing (padrão: transactional). Valores diferentes retornam 422.
variablesobjectNãoVariáveis para renderizar no template ou no seu html customizado.
# Exemplo completo
POST /send-email
Content-Type: application/json

{
  "from":      "Sua Empresa <[email protected]>",
  "to":        "[email protected]",
  "subject":   "Bem-vindo à B20!",
  "template":  "welcome",
  "type":      "transactional",
  "variables": {
    "name": "João Silva",
    "link": "https://seusistema.com/comecar"
  }
}

// Response 200 — o e-mail entra na FILA e é enviado pelo worker em segundos.
{
  "success":    true,
  "message":    "Email enfileirado com sucesso",
  "request_id": "a1b2c3-...",
  "message_id": null
}
// message_id fica null na resposta (o envio é assíncrono). Consulte o
// status final e o message_id da AWS depois via GET /logs.
GET/logs
Retorna o histórico bruto de e-mails processados. Ideal para rotinas diárias de backup do lado do cliente (via Cron Job). Máximo de 100 registros por chamada.

Query Parameters

ParâmetroTipoObrig.Descrição
limitintNãoQuantidade de registros (Padrão: 50. Máx: 100).
offsetintNãoPaginação (Padrão: 0).
statusstringNãoFiltrar por status: sent, failed, queued.
# Exemplo de Requisição (cURL)
curl -X GET "https://api.b20mail.com/logs?limit=100&status=failed" \
     -H "x-api-key: SUA_CHAVE_AQUI"

// Response 200
{
  "success": true,
  "message": "Logs recuperados",
  "request_id": "xyz-...",
  "count": 100,
  "limit": 100,
  "offset": 0,
  "logs": [
    {
      "request_id": "abc-...",
      "recipient": "[email protected]",
      "status": "failed",
      "error_message": "Bounce detectado",
      "created_at": "2026-06-15 14:00:00"
    }
  ]
}
GET/stats
Retorna a matemática agregada e a timeline de envios do seu projeto. Perfeito para construir e renderizar seus próprios painéis analíticos.

Query Parameters

ParâmetroTipoObrig.Descrição
periodstringNãoPeríodo analisado: 24h, 7d, 30d, 90d (Padrão: 30d).
# Exemplo de Requisição
GET /stats?period=7d

// Response 200
{
  "success": true,
  "message": "Stats OK",
  "request_id": "xyz-...",
  "period": "7d",
  "overview": {
    "total": 1462,
    "sent": 1450,
    "failed": 12,
    "opened": 840
  },
  "timeline": [
    { "label": "2026-06-12", "total": 502, "sent": 500, "failed": 2, "opened": 280, "transactional": 300, "marketing": 200 },
    { "label": "2026-06-13", "total": 960, "sent": 950, "failed": 10, "opened": 560, "transactional": 650, "marketing": 300 }
  ]
}

TEMPLATES DISPONÍVEIS

TemplateUsoVariáveis principais
welcomeBoas-vindas ao novo usuárioname, link
recover-passwordRecuperação de senhaname, link
notificationNotificação genéricatitle, message, name, link (opcional)
defaultLayout genérico (fallback automático)subject, title, message, link, button_text, company_name
Importante: use exatamente estes nomes de variáveis (em inglês) dentro do objeto variables da requisição. Nomes diferentes não serão substituídos e o e-mail sairá com o campo em branco.

ENVIANDO SEU PRÓPRIO HTML (CUSTOM)

Se você não quiser usar os templates da B20Mail, você pode desenhar e enviar o seu próprio código HTML cru. Basta omitir a variável template e preencher a variável html na requisição JSON.

Mágica das Variáveis: Mesmo enviando seu próprio HTML cru, você ainda pode usar {{sua_var}} dentro dele e enviar no objeto variables. A B20Mail vai procurar e substituir as variáveis automaticamente antes de disparar.

SINTAXE DE VARIÁVEIS

Use {{variavel}} nos templates. As variáveis são sanitizadas automaticamente contra XSS.

<!-- No template HTML -->
<p>Olá, {{name}}!</p>
<a href="{{link}}">Recuperar senha</a>

TUTORIAL: CLASSE DE INTEGRAÇÃO COMPLETA (PHP)

Abaixo apresentamos um exemplo de classe Mailer.php pronta para uso em produção, ideal para projetos PHP (puros ou frameworks). Ela demonstra como ler as configurações do .env, processar o envio inteligente (priorizando arquivos HTML locais com fallback para a B20Mail) e tratar retornos e logs de erro.

1. Configuração do .env

# Arquivo .env do seu projeto
B20MAIL_API_URL="https://api.b20mail.com"
B20MAIL_API_KEY="SUA_CHAVE_AQUI"
MAIL_FROM_ADDRESS="Sua Empresa <[email protected]>"

2. Classe Mailer.php

<?php
/**
 * Classe de Integração SaaS — B20Mail
 */

class Mailer {
    /**
     * Dispara um e-mail via B20Mail API
     */
    public static function send(string $to, string $subject, array $variables = [], string $templateName = 'default', string $type = 'transactional'): bool {
        $apiKey = trim($_ENV['B20MAIL_API_KEY'] ?? getenv('B20MAIL_API_KEY') ?? '', '"\'');
        
        if (empty($apiKey)) {
            error_log('B20MAIL_API_KEY não configurada');
            return false;
        }

        $mailFrom = trim($_ENV['MAIL_FROM_ADDRESS'] ?? getenv('MAIL_FROM_ADDRESS') ?? '', '"\'');
        
        $payload = [
            'from'      => $mailFrom ? $mailFrom : 'Sua Empresa <[email protected]>',
            'to'        => $to,
            'subject'   => $subject,
            'type'      => $type,
            'variables' => array_merge($variables, ['email' => $to])
        ];

        // Tenta carregar o template HTML da pasta local. Se não achar, delega pro template global da B20Mail.
        $templateFile = __DIR__ . '/views/emails/' . $templateName . '.html';
        if (file_exists($templateFile)) {
            $payload['html'] = file_get_contents($templateFile);
        } else {
            $payload['template'] = $templateName;
        }

        $apiUrl = trim($_ENV['B20MAIL_API_URL'] ?? getenv('B20MAIL_API_URL') ?? '', '"\'');
        $apiUrl = rtrim($apiUrl ?: 'https://api.b20mail.com', '/');
        
        $ch = curl_init($apiUrl . '/send-email');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_HTTPHEADER     => [
                'x-api-key: ' . $apiKey,
                'Content-Type: application/json'
            ],
            CURLOPT_POSTFIELDS     => json_encode($payload)
        ]);
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode >= 200 && $httpCode < 300) {
            return true;
        }

        error_log('B20Mail Erro (HTTP ' . $httpCode . '): ' . $response);
        return false;
    }
}

EXEMPLOS DE INTEGRAÇÃO BÁSICA

PHP / cURL

<?php
$ch = curl_init('https://api.b20mail.com/send-email');
$payload = json_encode([
    'from'      => 'Sua Empresa <[email protected]>',
    'to'        => '[email protected]',
    'subject'   => 'Bem-vindo!',
    'template'  => 'welcome',
    'type'      => 'transactional',
    'variables' => ['name' => 'João', 'link' => 'https://seusistema.com/comecar'],
]);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $payload,
    CURLOPT_HTTPHEADER     => [
        'x-api-key: SUA_CHAVE_AQUI',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

JavaScript / Fetch

async function sendEmail() {
    const response = await fetch('https://api.b20mail.com/send-email', {
      method: 'POST',
      headers: {
        'x-api-key':    'SUA_CHAVE_AQUI',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        from:      'Sua Empresa <[email protected]>',
        to:        '[email protected]',
        subject:   'Bem-vindo!',
        template:  'welcome',
        type:      'transactional',
        variables: { name: 'João', link: 'https://seusistema.com/comecar' },
      }),
    });
    const data = await response.json();
}

Python / requests

import requests

payload = {
    "from":      "Sua Empresa <[email protected]>",
    "to":        "[email protected]",
    "subject":   "Bem-vindo!",
    "template":  "welcome",
    "type":      "transactional",
    "variables": {"name": "João", "link": "https://seusistema.com/comecar"},
}

response = requests.post(
    "https://api.b20mail.com/send-email",
    headers={
        "x-api-key":    "SUA_CHAVE_AQUI",
        "Content-Type": "application/json",
    },
    json=payload,
)
data = response.json()

GERAR API KEY

As API Keys são gerenciadas diretamente pelo Painel Administrativo. Acesse a aba API KEYS no dashboard para:

AçãoComo fazer
Criar nova keyO admin cria o projeto e gera a chave automaticamente via dashboard.
Ver keys ativasAba API KEYS no dashboard — mostra label, plano, status e métricas.
Ativar/DesativarBotão de toggle na tabela de keys (apenas superadmin).
QuickstartAo selecionar uma key, o painel exibe código pronto em Node.js, PHP e cURL com a chave injetada.
Atenção: Trate sua API Key como uma senha. Nunca exponha em repositórios públicos ou código frontend.

WEBHOOKS

A B20Mail pode notificar seu sistema em tempo real quando um e-mail sofre bounce (rejeição) ou complaint (spam). Basta cadastrar um endpoint HTTPS no painel ou via API.

Eventos Disponíveis

EventoTriggerDescrição
email.bouncedAWS SNS BounceE-mail rejeitado pelo servidor destino
email.complainedAWS SNS ComplaintDestinatário marcou como spam
webhook.testPainel AdminEvento de teste para validar conectividade

Formato do Payload

Todos os webhooks são enviados como POST com body JSON:

{
  "event":     "email.bounced",
  "timestamp": "2026-06-29T21:00:00Z",
  "data": {
    "message_id":     "0100018ff123abc-abcd-1234",
    "request_id":     "a1b2c3d4e5f6-123456",
    "recipient":      "[email protected]",
    "subject":        "Bem-vindo!",
    "type":           "Permanent",
    "diagnostic":     "Bounce: 550 5.1.1 User unknown",
    "original_event": "Bounce"
  }
}

Verificação de Assinatura (HMAC-SHA256)

Cada webhook inclui headers de assinatura para garantir autenticidade:

HeaderDescrição
X-B20Mail-EventTipo do evento (email.bounced, etc.)
X-B20Mail-TimestampUnix timestamp do envio
X-B20Mail-SignatureAssinatura sha256=HMAC(timestamp.payload, secret)
Segurança: Sempre valide a assinatura antes de processar o evento. Rejeite requisições com timestamp com mais de 5 minutos de diferença.

Exemplos de Handler

PHP

<?php
$secret    = 'whsec_seu_secret_aqui';
$payload   = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_B20MAIL_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_B20MAIL_SIGNATURE'] ?? '';

// Validar assinatura
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    die('Assinatura inválida');
}

$data  = json_decode($payload, true);
$event = $data['event'] ?? '';

match($event) {
    'email.bounced'    => handleBounce($data['data']),
    'email.complained' => handleComplaint($data['data']),
    default            => null,
};

http_response_code(200);
echo 'OK';

Node.js / Express

const crypto = require('crypto');

app.post('/webhook/b20mail', (req, res) => {
    const secret    = process.env.B20MAIL_WEBHOOK_SECRET;
    const timestamp = req.headers['x-b20mail-timestamp'];
    const signature = req.headers['x-b20mail-signature'];
    const payload   = JSON.stringify(req.body);

    const expected = 'sha256=' + crypto
        .createHmac('sha256', secret)
        .update(timestamp + '.' + payload)
        .digest('hex');

    if (expected !== signature) return res.status(401).send('Invalid');

    const { event, data } = req.body;
    console.log(`Evento: ${event}`, data);
    res.status(200).send('OK');
});

Python / Flask

import hmac, hashlib, json
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhook/b20mail', methods=['POST'])
def webhook():
    secret    = "whsec_seu_secret".encode()
    payload   = request.get_data(as_text=True)
    timestamp = request.headers.get('X-B20Mail-Timestamp', '')
    signature = request.headers.get('X-B20Mail-Signature', '')

    expected = 'sha256=' + hmac.new(
        secret, (timestamp + '.' + payload).encode(), hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        return 'Invalid', 401

    data = json.loads(payload)
    print(f"Evento: {data['event']}", data['data'])
    return 'OK', 200

Como Cadastrar seu Endpoint

Passo a passo:
  1. Acesse o Dashboard e clique na aba WEBHOOKS na barra lateral
  2. Clique em "+ NOVO ENDPOINT"
  3. Selecione o projeto (API Key) vinculado
  4. Informe a URL HTTPS do seu endpoint receptor
  5. Marque os eventos que deseja receber (email.bounced, email.complained)
  6. Clique em "CRIAR ENDPOINT"
  7. Copie o secret HMAC exibido — ele será mostrado apenas uma vez
Segurança: Armazene o secret em uma variável de ambiente (B20MAIL_WEBHOOK_SECRET). Se perdido, remova o endpoint e crie um novo para gerar um novo secret.

CÓDIGOS DE ERRO

HTTPSignificadoCausa comum
400Bad RequestJSON malformado no body
401UnauthorizedHeader x-api-key ausente ou inválido
404Not FoundRota inexistente (confira a URL)
405Method Not AllowedMétodo HTTP errado para a rota. O header Allow da resposta informa quais são aceitos — /send-email só aceita POST.
422UnprocessableEmail inválido, template inexistente, campo ausente
429Too Many RequestsRate limit excedido para esta chave
500Server ErrorFalha na AWS SES ou banco de dados
Validando o endpoint na sua ferramenta: algumas plataformas de integração testam a URL com GET antes de usá-la. Como /send-email aceita apenas POST, elas recebem 405 — o que significa "a rota existe, use outro método", e não que o endpoint esteja fora do ar. Para um teste de disponibilidade, use GET /ping, que é público e responde 200.