Arquitetura
Como os serviços se dividem e conversam.
Arquitetura do Ecossistema Asender (v2 - Decentralizada)
Stack Geral
- Backend services: Go 1.22+ (stdlib HTTP + chi router)
- Frontends: TypeScript + Next.js 15 (App Router) + Tailwind + shadcn/ui
- Relay edge: PHP (unico caso PHP, ver
asender-relay-php/) - Database: PostgreSQL 16 (shared instance, schema por servico OU DB por servico)
- Cache: Redis 7
- Async queue: NATS JetStream (streams + key-value + object store)
- Object storage: S3-compatible (attachments, assets)
- Analytics store: ClickHouse (events em massa)
Visao Geral
┌─────────────────┐ ┌─────────────────┐
│ BACKOFFICE │ │ DASHBOARD │
│ (Next.js) │ │ (Next.js) │
│ Asender team │ │ Customers │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
┌────────────────────────────────────────┐
│ asender-api (Go) │
│ Public API + Dashboard BFF gateway │
│ Validates + Dispatches │
└───┬──────────┬──────────┬──────────┬──┘
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ AUTH │ │ CORE │ │ NATS │ │ REDIS │
│ (Go) │ │ (Go) │ │ JS │ │ │
└────────┘ └────────┘ └────────┘ └────────┘
│
┌─────────────┼─────────────┬─────────────┐
▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ EMAIL │ │ SMS │ │ PUSH │ │ WEBHOOK│
│ WORKER │ │ WORKER │ │ WORKER │ │ WORKER │
└───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌─────────┐ ┌────────┐ ┌────────┐
│ RELAY │ │ Twilio │ │ FCM │ │Customer│
│ PHP │ │ / SNS │ │ /APNs │ │ HTTPS │
└────────┘ └─────────┘ └────────┘ └────────┘
Servicos (Repositorios Separados)
Cada servico eh um diretorio/repo independente. Desenho baseado em perfis de carga distintos.
| Servico | Linguagem | Perfil de Carga | Scaling |
|---|---|---|---|
asender-auth | Go | Baixo, picos em login | 2-4 replicas |
asender-core | Go | Baixo, business logic | 1-2 replicas |
asender-api | Go | Muito alto (ingress publico) | 5-20 replicas |
asender-email-worker | Go | Bursty (campanhas) | 2-10 replicas |
asender-sms-worker | Go | Bursty | 1-5 replicas |
asender-push-worker | Go | Spikey (blasts) | 1-5 replicas |
asender-webhook-worker | Go | Bursty (egress) | 2-5 replicas |
asender-backoffice | TS/Next | Baixo (team interno) | 1 replica |
asender-dashboard | TS/Next | Medio (customers) | 2-4 replicas |
asender-shared | Go lib | - | - |
asender-relay-php | PHP | Edge (deploy por customer) | - |
Comunicacao entre Servicos
Sincrona (HTTP)
- api -> auth: validar JWT/session
- api -> core: lookup de tenant/api_key
- backoffice/dashboard -> api: BFF pattern
- workers -> core: buscar config do tenant
- workers -> relay PHP: envio real
Protocolo: HTTP JSON. Auth inter-service via token service-to-service (JWT com claim svc=<name>).
Assincrona (NATS JetStream)
Streams:
emails.send- api publica, email-worker consomesms.send- api publica, sms-worker consomepush.send- api publica, push-worker consomeevents.*- eventos (email.delivered, email.opened, etc) - webhook-worker + analytics consomem
Request/Reply (NATS)
Para calls sincronos sem HTTP (e.g. validacao rapida de tenant).
Auth Flow
Customer (dashboard)
1. POST /api/auth/login (dashboard BFF) -> asender-api -> asender-auth
2. asender-auth valida credenciais, cria session, retorna JWT
3. Dashboard guarda JWT em httpOnly cookie
4. Proximas requests mandam JWT via cookie
API publica (SDK do customer)
1. Customer app manda header Authorization: Bearer sk_live_xxx
2. asender-api valida API key contra asender-core
3. asender-api cria contexto de tenant + scope
4. Roteia request pro handler apropriado
Internal team (backoffice)
Similar ao customer flow, mas com role = asender_admin no JWT.
Acesso a endpoints /api/admin/* que operam cross-tenant.
Service-to-service
Cada servico tem um key de servico (SERVICE_SECRET env var).
Requests internas usam header X-Asender-Svc-Token: <jwt signed with shared secret>.
Claims: { svc, iss, exp, iat }.
Database Strategy
Shared Postgres instance, schema por servico.
auth.*- users, sessions, api_tokens, 2fa_secretscore.*- tenants, tenant_members, api_keys, relays, webhooks, usage_countersmessages.*- email_messages, email_events, sms_messages, push_messages (acessado por workers + api)analytics.*- events (ClickHouse, nao Postgres)
Isolation via schema + roles Postgres. Cada servico tem role propria com acesso so ao seu schema + referencias a dados de outros services via ID.
Trade-off: migracao pra DB-per-service se um deles precisar.
IDs Publicos
Convencao: <prefixo>_<16 hex> (usando crypto/rand).
| Prefixo | Recurso |
|---|---|
usr_ | User |
acc_ | Tenant (account) |
key_ | API Key |
sk_live_ | API Key plaintext |
sk_test_ | API Key test mode |
sk_master_ | Relay master key |
rel_ | Relay |
whk_ | Webhook endpoint |
tpl_ | Template |
cam_ | Campaign |
cnt_ | Contact |
lst_ | List |
msg_ | EmailMessage |
sms_ | SmsMessage |
psh_ | PushMessage |
evt_ | Event |
whd_ | Webhook delivery |
req_ | Request (tracing) |
Deployment
Cada servico:
- Dockerfile multi-stage (build + runtime)
- Binario estatico Go (scratch ou distroless base)
- Health endpoint
/healthz, ready endpoint/readyz - Metrics endpoint
/metrics(Prometheus format) - Structured logs (JSON, stdout)
- Config 100% via env vars (12-factor)
Orchestracao: Kubernetes (prod), docker-compose (dev), Fly.io ou Railway (staging).
Convencoes Comuns (asender-shared)
Lib asender-shared (Go) expoe:
- Types:
Tenant,ApiKey,User,Message(shared DTOs) - Errors: formato SES-compativel
{Error: {Type, Code, Message}} - IDs: helper
GenerateID(prefix string) string - Time: helpers
ISO8601Now()etc - Auth middleware: valida JWT service-to-service
- Tracing: request ID gen + propagation
- Logging: factory padronizada
- Config: envconfig wrapper
Observabilidade
- Logs: JSON pro stdout, agregados em Loki
- Metricas: Prometheus (request rate, latency, errors, queue depth)
- Traces: OpenTelemetry (opcional)
- Errors: Sentry
- Uptime: ping no
/healthzde cada servico
Seguranca
- TLS obrigatorio em producao (terminado no ingress)
- Secrets: via env vars, Vault/SOPS em prod
- API keys: hash SHA-256 no DB
- Service tokens: HMAC-SHA256 signed JWT, rotacao periodica
- CORS: default deny, allowlist por tenant
- Rate limit: por IP (gateway) + por API key (api service) + por tenant (core)
- Encryption at rest: DKIM keys, SMTP passwords, relay master keys
Frontend Architecture
Backoffice (interno Asender)
- Next.js 15 App Router
- Auth via
asender-auth+ roleasender_admin - Features: gestao de customers, monitoramento global, dashboards
- Rotas apenas no SSR + server actions calling
asender-api
Dashboard (customer-facing)
- Next.js 15 App Router
- Auth via
asender-auth+ role usuario normal - Features: inbox de emails, campanhas, templates, contatos, relatorios, config
- Rotas SSR + API routes locais que proxyam pro
asender-api
Shared UI
Cada frontend tem seu proprio design system mas compartilham design tokens via @asender/design-tokens (opcional, mais tarde).
Roadmap por Fase (Revisado)
Ver ROADMAP.md.