Payload CMS Adapter
The @smta/payload adapter wires SMTA’s auth interface to Payload CMS’s session context via a PostgreSQL session variable, allowing SMTA to run alongside Payload without interfering with Payload’s own data layer.
What It Contains
Section titled “What It Contains”| File | Purpose |
|---|---|
roles.sql | Optional role-membership wiring — grants app_user/app_admin to your login roles via the smta.app_login_role / smta.admin_login_role GUCs |
auth_payload_impl.sql | Implements core.get_current_user_id() via the app.current_user_id session variable |
secrets_pgcrypto_impl.sql | pgcrypto-backed secrets (store/read/delete) keyed off the app.secrets_key GUC |
Auth Implementation
Section titled “Auth Implementation”CREATE OR REPLACE FUNCTION core.get_current_user_id()RETURNS UUID AS $$ SELECT NULLIF(current_setting('app.current_user_id', true), '')::UUID;$$ LANGUAGE sql STABLE SECURITY DEFINER SET search_path = core, public;Unlike Supabase, Payload does not set a database-level session variable automatically. Your application middleware must set it using the injectUserContext helper from @smta/payload:
import { injectUserContext } from '@smta/payload'
// Call before any SMTA-guarded query within the same transactionawait injectUserContext(db, userId)The helper uses set_config with a parameterized query — safe from SQL injection:
export async function injectUserContext(db: DbExecutor, userId: string): Promise<void> { await db.query(`SELECT set_config('app.current_user_id', $1, true)`, [userId])}Secrets
Section titled “Secrets”The Payload adapter ships a pgcrypto-backed secrets implementation (secrets_pgcrypto_impl.sql), so core.store_secret_impl(), core.read_secret_impl(), and core.delete_secret_impl() are fully implemented. Secret values are encrypted with pgp_sym_encrypt into core.encrypted_secrets and referenced by a generated UUID.
The symmetric key is read from the app.secrets_key GUC, which your backend must set per session/connection — it is never hard-coded in SQL. If app.secrets_key is unset, storing or reading a secret raises an exception.
-- Set once per connection, before calling the secrets functionsSELECT set_config('app.secrets_key', '<your-encryption-key>', false);Schema Compatibility
Section titled “Schema Compatibility”Payload CMS manages its own tables in the public schema. SMTA uses core, platform, utils, and public (functions only — no tables). These schemas do not overlap. Payload’s collections and SMTA’s tenant infrastructure coexist without conflict.
Calling Public Functions from Payload
Section titled “Calling Public Functions from Payload”From your Payload collection hooks or route handlers:
const { rows } = await db.execute( sql`select * from public.list_my_organizations()`)Deployment
Section titled “Deployment”npm run build:payload # or: npx @smta/cli --adapter payload# → output/SMTA-payload-<timestamp>.sql (60 files)