Pular para o conteúdo principal

Sistema de E-mail de Boas-Vindas — Documentação Completa

APIs relacionadas: password-reset / first-access, templates, catálogo. Variáveis: .env.example (SMTP_*, WELCOME_EMAIL_*).

Visão Geral

O sistema de e-mail de boas-vindas é disparado automaticamente ao registar um utilizador via POST /api/v1/auth/register. Ele envia um e-mail com um link seguro para o utilizador definir a sua palavra-passe no primeiro acesso.


Fluxo Completo

┌──────────────────────────────────────────────────────────────────────┐
│ Admin chama POST /api/v1/auth/register │
│ ↓ │
│ RegisterService.registerUserBase() │
│ ↓ │
│ ┌─ enviarSenhaAleatoriaPorEmail === true? │
│ │ SIM → envia e-mail com senha em texto claro (outro fluxo) │
│ │ NÃO → WelcomeEmailService.sendAfterUserCreated() │
│ └───────────────────────────────────────────────────────────────────│
│ ↓ │
│ 1. Busca template ativo na BD (global_email_template_welcome) │
│ 2. Gera código aleatório de 6 dígitos (crypto.randomInt) │
│ 3. Faz hash bcrypt do código e grava em logs_password_resets │
│ 4. Encripta {email, codigo, exp} com AES-256-GCM → token │
│ 5. Constrói link: pageUrl + ?token=v1.XXXX │
│ 6. Substitui {{placeholders}} no template (assunto + corpo) │
│ 7. Gera HTML inline (tabelas + estilos embutidos) │
│ 8. Envia via SMTP (Nodemailer) │
│ ↓ │
│ Utilizador recebe e-mail → clica no link │
│ ↓ │
│ Frontend chama GET /api/v1/auth/first-access/token?token=... │
│ ↓ │
│ Frontend exibe formulário → utilizador define senha │
│ ↓ │
│ Frontend chama POST /api/v1/auth/first-access/confirm │
│ ↓ │
│ Valida código + atualiza password + marca log como usado │
└──────────────────────────────────────────────────────────────────────┘

Variáveis de Ambiente

E-mail de Boas-Vindas

VariávelTipoDefaultDescrição
WELCOME_EMAIL_NOME_EMPRESAstring'Arkus'Nome da empresa exibido no assunto, corpo e HTML do e-mail
WELCOME_EMAIL_LINK_TTL_SECONDSnumber604800 (7 dias)Tempo de vida do link de primeiro acesso, em segundos
WELCOME_EMAIL_FIRST_ACCESS_PAGE_URLstring(ver resolução)URL da página de front-end onde o utilizador define a senha
WELCOME_EMAIL_SCOPE_UUIDUUID'a0000001-0000-4000-8000-000000000001'Scope para filtrar qual template usar na BD
APP_URLstringFallback: gera {APP_URL}/primeiro-acesso se a URL explícita não estiver definida

SMTP (Transporte)

VariávelTipoDefaultDescrição
SMTP_HOSTstring'smtp.hostinger.com'Servidor SMTP
SMTP_PORTnumber587Porta SMTP
SMTP_SECUREbooleanfalseConexão SSL direta (porta 465)
SMTP_REQUIRE_TLSbooleantrueExige STARTTLS após conectar
SMTP_USERstring'suporte@seuappfacil.com.br'Utilizador de autenticação SMTP
SMTP_PASSstring(embarcado)Senha SMTP
SMTP_FROMstring(igual ao SMTP_USER)Endereço "De:" no e-mail
VariávelTipoDefaultDescrição
PASSWORD_RESET_LINK_SECRETstringChave para encriptar/decriptar tokens AES-256-GCM
JWT_SECRETstringFallback se PASSWORD_RESET_LINK_SECRET não estiver definido

Resolução da URL de Primeiro Acesso

Prioridade (de cima para baixo):

  1. Campo first_access_page_url no template da BD (se preenchido)
  2. WELCOME_EMAIL_FIRST_ACCESS_PAGE_URL (variável de ambiente)
  3. APP_URL + /primeiro-acesso (construído a partir do APP_URL)
  4. Default fixo: https://arkus.gevtech.com.br/primeiro-acesso

Tabelas na Base de Dados

global_governance_config_scope

Tabela de scopes (multi-tenant). Cada template pertence a um scope.

ColunaTipoDescrição
uuidUUID (PK)Identificador do scope
scope_typeVARCHAR(32)Tipo: 'platform', 'company', etc.
company_uuidUUID (nullable)Se tipo company, vincula a uma empresa
created_atTIMESTAMPTZData de criação
updated_atTIMESTAMPTZÚltima atualização

global_email_template_welcome

Template editável do e-mail de boas-vindas.

ColunaTipoDescrição
scope_uuidUUID (PK)FK para global_governance_config_scope.uuid
assuntoVARCHAR(200)Assunto do e-mail (aceita placeholders)
corpoTEXTCorpo texto do e-mail (aceita placeholders)
first_access_page_urlVARCHAR(500) (nullable)Override da URL de primeiro acesso
updated_atTIMESTAMPTZÚltima edição
updated_byUUID (nullable)Admin que editou

Placeholders aceitos:

PlaceholderAlias legadoValor
{{nome_empresa}}Nome da empresa (env ou input)
{{nome_usuario}}{{nomeUsuario}}DisplayName ou nomeCompleto do utilizador
{{link_primeiro_acesso}}{{linkPrimeiroAcesso}}URL completa com token criptografado

logs_password_resets

Log de códigos gerados (usado tanto para reset de senha quanto para primeiro acesso).

ColunaTipoDescrição
uuidUUID (PK)Identificador do log
tenant_idUUIDTenant fixo da plataforma
emailVARCHAR(255)E-mail do utilizador
token_hashVARCHAR(255)Hash bcrypt do código de 6 dígitos
usadoBOOLEANfalse → pendente; true → já utilizado
expira_emTIMESTAMPTZData/hora de expiração
metadataJSONB{ userId, solicitationType, scopeUuid }
created_atTIMESTAMPTZData de criação

Valor de metadata.solicitationType: 'welcome_first_access'


APIs

1. Registar utilizador (dispara o e-mail)

POST /api/v1/auth/register
  • Acesso: Público
  • Ação: Cria utilizador e dispara e-mail de boas-vindas (assíncrono)
  • Condição: enviarSenhaAleatoriaPorEmail !== true no body/metadata
GET /api/v1/auth/first-access/token?token=v1.XXXX
  • Acesso: Público
  • Ação: Decifra o token AES-256-GCM e devolve os dados
  • Response 200:
{
"email": "maria@empresa.pt",
"codigo": "483920",
"expiraEm": "2026-08-12T22:50:00.000Z"
}
  • Erros: 400 (token inválido ou expirado)

3. Confirmar palavra-passe (primeiro acesso)

POST /api/v1/auth/first-access/confirm
  • Acesso: Público
  • Ação: Valida o código, define a nova senha, marca log como usado

Body (opção 1 — com token):

{
"token": "v1.AABBCC...",
"newPassword": "minhaSenhaSegura123"
}

Body (opção 2 — com email + código manual):

{
"email": "maria@empresa.pt",
"codigo": "483920",
"newPassword": "minhaSenhaSegura123"
}
  • Response: 204 No Content (sucesso)
  • Erros: 400 (código inválido/expirado), 404 (utilizador não encontrado)

Validações da nova senha:

  • Mínimo: 8 caracteres
  • Máximo: 128 caracteres

4. Gerir templates (admin)

GET /api/v1/email-templates/waving_hand
PATCH /api/v1/email-templates/waving_hand
  • Acesso: JWT + role administrador de plataforma
  • Chave: waving_hand mapeia para global_email_template_welcome

Body PATCH (parcial):

{
"assunto": "Bem-vindo(a) à {{nome_empresa}}, {{nome_usuario}}!",
"corpo": "Olá {{nome_usuario}}, ...",
"firstAccessPageUrl": "https://meusite.com/primeiro-acesso"
}

Segurança — Detalhes Técnicos

Código de 6 dígitos

  • Gerado com crypto.randomInt(100000, 999999) (CSPRNG)
  • Nunca armazenado em texto claro → hash bcrypt (10 rounds)
  • Validação com bcrypt.compare(code, tokenHash) no consumo
AspectoValor
Algoritmoaes-256-gcm
IV12 bytes aleatórios
Auth Tag16 bytes
Derivação da chaveSHA-256(secret) → 32 bytes
Payload{ email, codigo, exp } (JSON)
Formato finalv1.{base64url(iv + authTag + ciphertext)}

Fluxo de encriptação:

  1. Gera IV aleatório de 12 bytes
  2. Deriva chave de 256 bits via SHA-256 do secret
  3. Encripta payload JSON com AES-256-GCM
  4. Concatena IV + authTag + ciphertext
  5. Codifica em base64url, prefixado com v1.

Fluxo de decriptação:

  1. Separa versão (v1) do body
  2. Decodifica base64url
  3. Extrai IV (12 bytes), authTag (16 bytes) e ciphertext
  4. Decripta com a mesma chave derivada
  5. Valida integridade (GCM auth tag)
  6. Parse JSON → valida campos

Expiração (dupla camada)

  1. No token: campo exp (epoch ms) → verificado no GET /token
  2. Na BD: campo expira_em → verificado no POST /confirm

HTML do E-mail

O e-mail HTML usa tabelas inline (máxima compatibilidade com clientes como Gmail, Outlook, etc.):

  • Cabeçalho com gradiente (nome da empresa + badge "Bem-vindo(a)!")
  • Corpo com saudação personalizada
  • Botão CTA azul com link de primeiro acesso
  • Caixa com link alternativo (copiar/colar)
  • Nota de segurança
  • Rodapé com nome da empresa

Todos os valores dinâmicos passam por escapeHtml() (previne XSS).


Seed SQL

Para popular o template inicial na BD:

-- Criar scope
INSERT INTO public.global_governance_config_scope (uuid, scope_type, company_uuid)
VALUES ('22222222-2222-4222-8222-222222222222'::uuid, 'platform', NULL)
ON CONFLICT (uuid) DO NOTHING;

-- Criar template
INSERT INTO public.global_email_template_welcome (scope_uuid, assunto, corpo, updated_by)
VALUES (
'22222222-2222-4222-8222-222222222222'::uuid,
'Bem-vindo(a) ao Arkus, {{nomeUsuario}}!',
'Olá {{nomeUsuario}},

A sua conta no Arkus foi criada com sucesso. Para definir a palavra-passe e aceder à plataforma, utilize o link abaixo:

{{linkPrimeiroAcesso}}

Este link é válido por 7 dias.',
NULL
);

Depois, no .env:

WELCOME_EMAIL_SCOPE_UUID=22222222-2222-4222-8222-222222222222

Exemplo de .env Completo

# === E-mail de Boas-Vindas ===
WELCOME_EMAIL_NOME_EMPRESA=MinhaEmpresa
WELCOME_EMAIL_LINK_TTL_SECONDS=604800
WELCOME_EMAIL_FIRST_ACCESS_PAGE_URL=https://app.minhaempresa.com/primeiro-acesso
WELCOME_EMAIL_SCOPE_UUID=22222222-2222-4222-8222-222222222222

# === SMTP ===
SMTP_HOST=smtp.hostinger.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_REQUIRE_TLS=true
SMTP_USER=noreply@minhaempresa.com
SMTP_PASS=suaSenhaAqui
SMTP_FROM=noreply@minhaempresa.com

# === Segurança do Token ===
PASSWORD_RESET_LINK_SECRET=uma-chave-secreta-longa-e-aleatoria
# Ou fallback:
JWT_SECRET=meu-jwt-secret

# === URL da aplicação (fallback) ===
APP_URL=https://app.minhaempresa.com

Checklist para Replicar em Outro Sistema

  • Criar tabela de scopes (governança multi-tenant)
  • Criar tabela de templates com placeholders
  • Criar tabela de logs (hash + expiração + flag usado)
  • Implementar geração de código seguro (crypto.randomInt)
  • Implementar hash bcrypt para o código
  • Implementar encriptação AES-256-GCM para o token do link
  • Implementar motor de template (regex {{...}} → substituição)
  • Implementar HTML do e-mail (tabelas inline)
  • Configurar transporte SMTP (Nodemailer)
  • Implementar endpoint de decifrar token (GET)
  • Implementar endpoint de confirmar senha (POST)
  • Implementar endpoint de gestão de templates (GET/PATCH, admin)
  • Configurar variáveis de ambiente
  • Executar seed SQL na BD