Files
ipal-kit/docs/email.md
T

5.6 KiB

email

Wysyłka maili z dwoma transportami do wyboru: SMTP z panelu (panelSmtpAdapter, uniwersalny) albo Microsoft Graph (graphAdapter, przez Exchange/M365). Oba implementują ten sam interfejs PayloadEmailAdapter, więc payload.sendEmail i maile z formularzy działają niezależnie od wyboru. Klient/projekt wybiera transport w configu.

Zależność

"dependencies": { "nodemailer": "^6.9.0" }

(+ @types/nodemailer w devDependencies)

Config

Brak opcji — SMTP (host, port, user, password, from) jest w SiteIntegrations (tab SMTP). Edytor konfiguruje w panelu.

Front — sendEmail (server)

import { sendEmail } from '@intecion/ipal-kit/server'

const result = await sendEmail({
  payload,
  to: '[email protected]',
  subject: 'Nowa wiadomość',
  html: '<h1>Cześć</h1><p>Treść…</p>',   // gotowy HTML (Twój wygląd)
  // text: 'wersja plain', from: '...', replyTo: '...'
})

if (result.sent) {
  // result.messageId
} else {
  // result.error — np. "SMTP is not configured..."
}
  • HTML to Twój argument — plugin nie ma templatek, wysyła to co podasz. Wygląd maila składasz na froncie.
  • Zwraca { sent: true, messageId } albo { sent: false, error } — nie rzuca wyjątku, nie wycieka detali SMTP do klienta.
  • Port 465 → implicit TLS, inne → STARTTLS.
  • server-only — hasło SMTP nigdy w bundlu przeglądarki.

Uwaga: maile systemowe Payloada

Reset hasła / weryfikacja email idą przez wbudowany mechanizm Payload (config.email), którego ten moduł nie konfiguruje (celowo — wymagałby SMTP w env). Jeśli ich potrzebujesz, to osobna konfiguracja adaptera przy buildConfig.

Adapter — SMTP z panelu dla całego Payloada

sendEmail wysyła własnym transportem. Osobno plugin daje adapter, który podłącza tę samą skrzynkę pod payload.sendEmail:

// payload.config.ts
import { panelSmtpAdapter } from '@intecion/ipal-kit'

export default buildConfig({
  email: panelSmtpAdapter(),
  // panelSmtpAdapter({ fallbackFromAddress, fallbackFromName }) — używane tylko
  // zanim panel zostanie wypełniony (Payload wymaga adresu synchronicznie przy
  // starcie, zanim można odczytać globala)
})

Po wpięciu wszystko, co w Payloadzie wysyła maile, idzie przez SMTP z Site Integrations — w tym wbudowane maile form-buildera (Forms → Emails), maile resetu hasła i weryfikacji konta. Adapter czyta konfigurację przy każdym wysłaniu, więc zmiana skrzynki w panelu działa bez restartu.

Bez adaptera Payload używa mocka, który tylko loguje do konsoli — maile form-buildera nie wyjdą.

Maskowanie sekretów w panelu (MaskedField)

Wrażliwe pola w Site Integrations (smtpPassword, r2SecretAccessKey, turnstileSecretKey) są maskowane w UI — pokazują •••• zamiast plaintextu, z przyciskiem Reveal/Hide. To maskowanie UI, NIE hashowanie ani szyfrowanie: wartość w bazie jest plaintext (musi być odzyskiwalna do autentykacji SMTP/R2). Chroni przed patrzeniem przez ramię i przypadkowym pokazaniem panelu.

Podpięte przez admin.components.Field: '@intecion/ipal-kit/client#MaskedField'. Działa na dowolnym polu text. Po wpięciu w projekcie może być konieczne payload generate:importmap, żeby panel rozpoznał komponent.

Główną ochroną sekretów pozostaje read: isAdmin na globalu SiteIntegrations (anonim nie dostaje). Maskowanie to warstwa dodatkowa (shoulder-surfing), nie ochrona bazy — przy wycieku DB sekrety są czytelne.

Adapter — Microsoft Graph (Exchange / M365)

Alternatywa dla SMTP: wysyłka przez Microsoft Graph API, przez skrzynkę w Waszym (agencyjnym) tenancie M365. Wszystkie maile z formularzy wszystkich projektów idą przez JEDNĄ skrzynkę nadawczą (np. [email protected]).

Podział konfiguracji (celowy)

Sekrety w .env (agencyjne — Wasz Exchange, klient nie widzi):

GRAPH_TENANT_ID=...
GRAPH_CLIENT_ID=...
GRAPH_CLIENT_SECRET=...
GRAPH_SENDER=[email protected]   # jedna skrzynka dla wszystkich projektów

From-display w panelu (per projekt): czyta istniejące smtpFromAddress / smtpFromName z SiteIntegrations — bo „from" to ten sam koncept niezależnie od transportu. Nie trzeba nowego pola.

Wpięcie — wybór transportu

// payload.config.ts
import { panelSmtpAdapter, graphAdapter } from '@intecion/ipal-kit'

email: process.env.GRAPH_CLIENT_ID
  ? graphAdapter()           // Graph, gdy sekrety w .env
  : panelSmtpAdapter(),      // SMTP z panelu (fallback)

Setup Azure / Exchange (jednorazowo, Wasza strona, POZA kodem)

  1. App registration w Azure AD → tenantId, clientId
  2. Client secret → clientSecret
  3. API Permissions → Microsoft Graph → Application → Mail.Send → Grant admin consent (bez tego: Insufficient privileges)
  4. Exchange Admin Center → skrzynka [email protected] → Mailbox Delegation → aplikacja do "Send As" (bez tego: ErrorAccessDenied)

Szczegóły techniczne

  • Auth: client credentials flow, scope https://graph.microsoft.com/.default (NIE Mail.Send — Azure odrzuca, AADSTS1002012)
  • Wysyłka: POST /users/{sender}/sendMail (NIE /me — app-only nie ma „me")
  • Sukces: HTTP 202 (pusty body)
  • Czysty REST (fetch), zero bibliotek Microsoft, zero nowych zależności

PUŁAPKA — from vs Send-As

Jeśli from w panelu = cudza domena (np. [email protected]), a sender = [email protected] — Exchange zablokuje, chyba że aplikacja ma Send-As na tę domenę. Najbezpieczniej: from = GRAPH_SENDER (Wasza skrzynka), a adres klienta w replyTo (odpowiedzi trafią do klienta). Wtedy Send-As na cudze domeny nie jest potrzebny.