# STANDARD AgileHub — Email Relay centralizzato (canonico) > **Autorità**: AgileHub (single source of truth per la suite Agile Software). > **Versione**: 1.0 — 2026-04-21 > **Stato**: ADOTTATO. Vincolante per tutti i prodotti della suite. > **Destinatari**: team TRPG, SustainAI, NIS2, TAXAI (AllTax), LG231, DFM, MKTG, ALLRISK, WMS, MADEBYCLOUD, CertiSource. > **Registry DB**: record master in `nexus_hub.hub_standards` (slug=`email-relay`, version=`1.0`). --- ## 1. Principio **Tutte le email generate dalla suite AgileHub passano da UN SOLO punto**: il microservizio `email-automation-ms` (PM2 id 21, porta 4004). Nessun prodotto, microservizio, container DevEnv, cron o agent AI può accedere direttamente a: - Postfix host Hetzner (`172.18.0.1:25`) - Server Gmail (`smtp-relay.gmail.com`, `smtp.gmail.com`) - Altri provider SMTP (SendGrid, Mailgun, AWS SES, ecc.) --- ## 2. Architettura ``` ┌──────────────────┐ HTTPS + X-Internal-Key │ Prodotto cliente │──────────────┐ │ (TRPG/SustAI...) │ │ └──────────────────┘ ▼ ┌──────────────────────┐ ┌──────────────────┐ │ email-automation-ms │ SMTP (internal) │ Microservizio │────▶│ porta 4004 │──────────┐ │ AgileHub │ │ │ ▼ │ (ticket/lead/..) │ │ Rate limit + Template│ ┌──────────────┐ └──────────────────┘ │ Audit DB + Retry │ │ Postfix host │ └──────────────────────┘ │ 172.18.0.1:25│ └──────┬───────┘ │ SASL auth ▼ ┌──────────────┐ │ Gmail │ │ smtp-relay │ │ .gmail.com │ └──────────────┘ ``` **Relay chain**: - Prodotto → `email-automation-ms:4004` (via Apache vhost `agilehub.agile.software/api/emails/*`) - `email-automation-ms` → Postfix `172.18.0.1:25` (container→host, senza auth perché `mynetworks` include `172.18.0.0/16`) - Postfix → Gmail SMTP relay `:587` (richiede SASL auth service-account Workspace) --- ## 3. Contratto API ### Endpoint canonico ``` POST https://agilehub.agile.software/api/emails/send Headers: X-Internal-Key: (shared via /etc/agilehub/internal-keys.env) Content-Type: application/json Body: { "to": "user@example.com", // string o array "from_tenant_id": 5, // int — attribuzione log "template": "demo-registration-verify", // nome template in email-automation-ms/templates/ "variables": { "name": "Mario", ... }, // placeholder per il template "product": "TRPG", // slug prodotto (audit) "reply_to": "support@trpg.agile.software", // opzionale "priority": "transactional" // "transactional" | "marketing" | "system" } ``` ### Response ```json { "success": true, "message_id": "", "queued_at": "2026-04-21T15:30:00.000Z" } ``` ### Log audit Ogni invio viene registrato in `nexus_email_db.email_log`: - `id, message_id, to, from, subject, template, product, tenant_id, status, sent_at, smtp_response, retry_count` ### Status delivery - `queued` — accettato dal service, in coda retry - `sent` — accettato dal Postfix host (risposta 250 OK) - `delivered` — accettato dal Gmail relay (webhook o check log mail.log) - `bounced` — Gmail ha rifiutato (bad recipient, spam, auth failure) - `failed_permanent` — 5xx, no retry - `failed_transient` — 4xx, retry backoff (max 3) --- ## 4. VIETATO Qualsiasi alternativa al percorso `email-automation-ms`. In particolare: ❌ **Connessione SMTP diretta** a `172.18.0.1:25` da container prodotto o DevEnv. ❌ **Provider cloud** (SendGrid, Mailgun, SES, Postmark) — se necessari, integrarli DENTRO `email-automation-ms` come alternative transport. ❌ **Librerie SMTP client** in codice prodotto: - JS: `nodemailer`, `emailjs`, `smtp-connection` - PHP: `PHPMailer`, `Swift_Mailer`, `mail()` built-in, `mb_send_mail()` - Python: `smtplib` usato con credenziali cleartext - Ruby, Go, ecc.: equivalenti ❌ **Binari di sistema** in container: `sendmail`, `mail`, `mutt`, `msmtp`. ❌ **File .env con `SMTP_*`** in qualsiasi servizio diverso da `email-automation-ms`. ### Deroghe Solo via ticket AgileHub tag `email-bypass-exception`. Motivazioni accettabili: - Integrazione legacy in dismissione con data di fine (es. mktg-agile oggi) - Volume massimo > 100k/mese che giustifica provider dedicato (richiede integrazione in email-automation-ms, non bypass) - Test E2E isolati (usa container mailpit dedicato, non produzione) Deroghe hanno scadenza obbligatoria (max 90gg). AgileHub tiene registro in `nexus_hub.hub_standards_adoption.exemption_reason/exemption_expires_at`. --- ## 5. Come integrare ### Lato prodotto (TRPG, SustainAI, ecc.) 1. Rimuovere ogni `SMTP_*` dal `.env` del prodotto 2. Aggiungere `INTERNAL_EMAIL_KEY=` al `.env` (coordinato con team AgileHub) 3. Sostituire chiamate dirette con `curl`/`fetch`/`requests` verso `https://agilehub.agile.software/api/emails/send` 4. Rimuovere dipendenze SMTP (`nodemailer`, `PHPMailer`, ecc.) dal manifest (`package.json`, `composer.json`) ### Lato AgileHub 1. `email-automation-ms` espone `/send` + `/templates/list` + `/webhooks/bounce` 2. Gestisce rate limit per tenant (`nexus_email_db.email_quota`) 3. Tiene in vita la connessione SMTP a Postfix (connection pool) 4. Retry exponential backoff su 4xx (1min, 5min, 30min), drop su 5xx 5. Webhook bounce da Gmail (future) → aggiorna `status=bounced` + notifica tenant admin --- ## 6. Stato attuale (2026-04-22 — FIX APPLICATO) ### ✅ Operativo end-to-end - Architettura centralizzata implementata e funzionante - IP allowlist Workspace attiva per `135.181.149.254` (configurata da Massimo Tagliavini) - `sendmail` locale: funziona (cron watchdog, claude-auth-check, ticket-agent) - **`email-automation-ms` canonical path**: funziona dopo fix 2026-04-22 - Endpoint protetti da `X-Internal-Key` (auth middleware aggiunto) - Env vars propagate in `/etc/agilehub/internal-keys.env` + `.env` di 5 prodotti produzione ### Fix applicato al transport Nodemailer File: `email-automation-ms/src/services/emailService.js:25-40` ```javascript transporter = nodemailer.createTransport({ host, // da SMTP_HOST=127.0.0.1 (era 172.18.0.1) port, secure, name: process.env.SMTP_HELO_NAME || 'agile.software', // HELO esplicito ... }); ``` File: `email-automation-ms/.env` ``` SMTP_HOST=127.0.0.1 # era 172.18.0.1 (docker bridge) → Postfix vedeva connessione "esterna" INTERNAL_SERVICE_KEY=nexus-internal-2026 # alias INTERNAL_EMAIL_KEY ``` ### Auth endpoint File: `email-automation-ms/src/index.js:27-55` — middleware `requireInternalKey`: - Protegge: `/emails/send`, `/emails/send-raw`, `/sequences/*`, `/emails/templates/db` (POST/PUT/DELETE) - Legge la chiave da `INTERNAL_EMAIL_KEY` (preferita) o `INTERNAL_SERVICE_KEY` (retrocompat cron) - Senza header → 401 UNAUTHORIZED - Con header valido → passa al controller - Se nessuna chiave configurata → warn + pass (fallback legacy) ### Test E2E superati (2026-04-22) | Test | Risultato | |------|-----------| | POST `/emails/send-raw` senza X-Internal-Key | 401 ✅ | | POST `/emails/send-raw` con X-Internal-Key (locale) | 201 + messageId + SENT in DB ✅ | | POST HTTPS `https://agilehub.agile.software/api/emails/send-raw` da container TRPG | 201 + SENT in DB, product=TRPG ✅ | | Consegna su Gmail `250 2.0.0 OK` via `smtp-relay.gmail.com:587` | ✅ confermato in `/var/log/mail.log` | ### ❌ Bypass noti - `/var/www/mktg-agile/.env` contiene ancora `SMTP_*` — tollerato perché prodotto in dismissione Fase C (spegnimento 2026-04-21+) --- ## 7. Obblighi prodotti della suite Per essere conforme a `email-relay` v1.0 ogni prodotto **DEVE**: 1. ✅ Rimuovere ogni `SMTP_*`, credenziali Gmail, API key mailer-cloud dal proprio `.env` 2. ✅ Eliminare dipendenze mailer (nodemailer, PHPMailer, ecc.) dal manifest 3. ✅ Sostituire ogni invio con POST verso `https://agilehub.agile.software/api/emails/send` 4. ✅ Ottenere `INTERNAL_EMAIL_KEY` da AgileHub team (via ticket tag `email-key-provision`) 5. ✅ Aggiungere `INTERNAL_EMAIL_KEY` al `.env` istanza con chmod 600 6. ✅ Usare template definiti in `email-automation-ms/templates/` (aprire PR per nuovi template) 7. ✅ Loggare `message_id` ricevuto nella response per debug Un prodotto non conforme entra in `pending` adoption nel registry. Deroghe formali con scadenza obbligatoria. --- ## 8. Riferimenti - Microservizio: `/var/www/agile-services/email-automation-ms/` (Hetzner) + `/projects/agile-services/email-automation-ms/` (container) - Entry point: `email-automation-ms/src/index.js:45` (SMTP transport config) - Template repo: `email-automation-ms/templates/` (Handlebars-like .hbs) - Log DB: `nexus_email_db.email_log` (MySQL host Hetzner) - Postfix config: `/etc/postfix/main.cf` (Hetzner host) - Gmail Workspace Admin Console: account `admin@agile.software` --- ## 9. Changelog ### v1.2 — 2026-04-22 (fix applicato — operativo) - Fix transport Nodemailer: `SMTP_HOST=127.0.0.1` (era `172.18.0.1` docker bridge) + `name: 'agile.software'` HELO esplicito - Aggiunto middleware `requireInternalKey` su `/emails/send*`, `/sequences/*`, `/emails/templates/db` mutazioni — chiave `INTERNAL_SERVICE_KEY=nexus-internal-2026` (alias `INTERNAL_EMAIL_KEY`) - Registrato `INTERNAL_EMAIL_KEY` in `/etc/agilehub/internal-keys.env` - Propagato `INTERNAL_EMAIL_KEY` + `EMAIL_MS_URL` al `.env` di 5 prodotti produzione (trpg-agile, sustainai-agile, nis2-agile, mktg-agile, allrisk-agile). Altri 5 prodotti (trpg-pro-agile, taxai-agile, lg231-agile, dfm-pro-agile, wms-agile) non hanno `.env` in produzione — useranno la registry centrale. - Test E2E passati: invio da container TRPG via HTTPS verso `agilehub.agile.software/api/emails/send-raw` → consegnato Gmail 250 OK. ### v1.1 — 2026-04-22 (correzione narrativa, pre-fix) - Correzione narrativa sezione 6: SMTP NON è bloccato. Il path `sendmail` funziona (Massimo ha configurato IP allowlist da tempo). Il bug reale era in `email-automation-ms` transport. - Rimosso "fix A/B Postfix SASL" (errore di analisi del 2026-04-21 — non serviva). ### v1.0 — 2026-04-21 - Versione iniziale — formalizza architettura esistente - Distribuita a 11 prodotti suite via DB registry `hub_standards` - Richiesta conformità prodotti: rimuovere SMTP diretti dove presenti --- **Firma autorità**: AgileHub. Violazioni vanno in `hub_standards_distribution_log` con `result='non_compliant'`.