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ável | Tipo | Default | Descrição |
|---|---|---|---|
WELCOME_EMAIL_NOME_EMPRESA | string | 'Arkus' | Nome da empresa exibido no assunto, corpo e HTML do e-mail |
WELCOME_EMAIL_LINK_TTL_SECONDS | number | 604800 (7 dias) | Tempo de vida do link de primeiro acesso, em segundos |
WELCOME_EMAIL_FIRST_ACCESS_PAGE_URL | string | (ver resolução) | URL da página de front-end onde o utilizador define a senha |
WELCOME_EMAIL_SCOPE_UUID | UUID | 'a0000001-0000-4000-8000-000000000001' | Scope para filtrar qual template usar na BD |
APP_URL | string | — | Fallback: gera {APP_URL}/primeiro-acesso se a URL explícita não estiver definida |
SMTP (Transporte)
| Variável | Tipo | Default | Descrição |
|---|---|---|---|
SMTP_HOST | string | 'smtp.hostinger.com' | Servidor SMTP |
SMTP_PORT | number | 587 | Porta SMTP |
SMTP_SECURE | boolean | false | Conexão SSL direta (porta 465) |
SMTP_REQUIRE_TLS | boolean | true | Exige STARTTLS após conectar |
SMTP_USER | string | 'suporte@seuappfacil.com.br' | Utilizador de autenticação SMTP |
SMTP_PASS | string | (embarcado) | Senha SMTP |
SMTP_FROM | string | (igual ao SMTP_USER) | Endereço "De:" no e-mail |
Segurança (Token do Link)
| Variável | Tipo | Default | Descrição |
|---|---|---|---|
PASSWORD_RESET_LINK_SECRET | string | — | Chave para encriptar/decriptar tokens AES-256-GCM |
JWT_SECRET | string | — | Fallback se PASSWORD_RESET_LINK_SECRET não estiver definido |
Resolução da URL de Primeiro Acesso
Prioridade (de cima para baixo):
- Campo
first_access_page_urlno template da BD (se preenchido) WELCOME_EMAIL_FIRST_ACCESS_PAGE_URL(variável de ambiente)APP_URL+/primeiro-acesso(construído a partir do APP_URL)- 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.
| Coluna | Tipo | Descrição |
|---|---|---|
uuid | UUID (PK) | Identificador do scope |
scope_type | VARCHAR(32) | Tipo: 'platform', 'company', etc. |
company_uuid | UUID (nullable) | Se tipo company, vincula a uma empresa |
created_at | TIMESTAMPTZ | Data de criação |
updated_at | TIMESTAMPTZ | Última atualização |
global_email_template_welcome
Template editável do e-mail de boas-vindas.
| Coluna | Tipo | Descrição |
|---|---|---|
scope_uuid | UUID (PK) | FK para global_governance_config_scope.uuid |
assunto | VARCHAR(200) | Assunto do e-mail (aceita placeholders) |
corpo | TEXT | Corpo texto do e-mail (aceita placeholders) |
first_access_page_url | VARCHAR(500) (nullable) | Override da URL de primeiro acesso |
updated_at | TIMESTAMPTZ | Última edição |
updated_by | UUID (nullable) | Admin que editou |
Placeholders aceitos:
| Placeholder | Alias legado | Valor |
|---|---|---|
{{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).
| Coluna | Tipo | Descrição |
|---|---|---|
uuid | UUID (PK) | Identificador do log |
tenant_id | UUID | Tenant fixo da plataforma |
email | VARCHAR(255) | E-mail do utilizador |
token_hash | VARCHAR(255) | Hash bcrypt do código de 6 dígitos |
usado | BOOLEAN | false → pendente; true → já utilizado |
expira_em | TIMESTAMPTZ | Data/hora de expiração |
metadata | JSONB | { userId, solicitationType, scopeUuid } |
created_at | TIMESTAMPTZ | Data 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 !== trueno body/metadata
2. Decifrar token do link
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_handmapeia paraglobal_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
Token do link (AES-256-GCM)
| Aspecto | Valor |
|---|---|
| Algoritmo | aes-256-gcm |
| IV | 12 bytes aleatórios |
| Auth Tag | 16 bytes |
| Derivação da chave | SHA-256(secret) → 32 bytes |
| Payload | { email, codigo, exp } (JSON) |
| Formato final | v1.{base64url(iv + authTag + ciphertext)} |
Fluxo de encriptação:
- Gera IV aleatório de 12 bytes
- Deriva chave de 256 bits via SHA-256 do secret
- Encripta payload JSON com AES-256-GCM
- Concatena IV + authTag + ciphertext
- Codifica em base64url, prefixado com
v1.
Fluxo de decriptação:
- Separa versão (
v1) do body - Decodifica base64url
- Extrai IV (12 bytes), authTag (16 bytes) e ciphertext
- Decripta com a mesma chave derivada
- Valida integridade (GCM auth tag)
- Parse JSON → valida campos
Expiração (dupla camada)
- No token: campo
exp(epoch ms) → verificado noGET /token - Na BD: campo
expira_em→ verificado noPOST /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