Skip to content

Epic: Build the multi-tenant Azure Functions email API #2

Description

@patoperpetua

Objective

Implement the PostKit runtime API as a Node.js/TypeScript Azure Functions application that accepts trusted email requests, resolves the authenticated tenant, loads the correct published template, renders variables, and sends through a provider abstraction.

Context

PostKit is a shared transactional email platform for multiple PoCs and trusted server-side consumers. Consumers include backend APIs, marketing-site server endpoints, server actions, scheduled jobs, and other trusted services.

The API must never trust a raw caller-provided tenant identifier as the sole tenant boundary.

Required technology

  • Azure Functions
  • Node.js >= 20
  • TypeScript + ESM
  • pnpm workspace package under apps/api
  • Shared request/response contracts from @singleton-sd/post-kit-types
  • Azure SDKs for managed services where applicable
  • Structured logging and correlation IDs
  • Provider abstraction for email delivery

Core API contract

Initial endpoint concept:

POST /emails/send

Example trusted request:

{
  "template": "marketing.contact-us",
  "to": "hello@example.com",
  "variables": {
    "name": "Jane Doe",
    "email": "jane@example.com",
    "message": "I'd like to know more"
  }
}

Tenant identity must be derived from authentication/credentials and mapped server-side.

Runtime flow

Authenticated consumer
  -> Azure Function
  -> resolve tenant + environment
  -> validate request
  -> load compiled template + metadata
  -> validate required variables
  -> merge tenant branding/default variables
  -> render subject + HTML
  -> send using configured provider
  -> structured result + correlation ID

Multi-tenancy requirements

  • Tenant identity is derived from trusted auth, API key mapping, managed identity, or equivalent server-side trust mechanism.
  • Never allow a consumer to gain access to another tenant by changing a tenant field in the payload.
  • Tenant-specific branding and templates are isolated by storage paths/keys.
  • Environment boundaries must also be explicit (development, staging, production or equivalent).
  • Design the tenant resolver behind an interface so authentication can evolve without rewriting core email logic.

Internal abstractions

Create clear interfaces/modules for:

  • TenantResolver
  • TemplateStore
  • TemplateRenderer
  • EmailProvider
  • request validation
  • variable validation
  • observability/logging

Do not couple Azure Blob Storage or one email provider directly to business logic.

Error handling

Return stable typed errors for at least:

  • unauthenticated/unauthorised consumer
  • unknown template
  • invalid template metadata
  • missing/invalid variables
  • invalid recipient
  • provider failure
  • storage failure

Do not leak provider credentials, storage paths, or sensitive payloads in responses/logs.

Reliability expectations

  • Log correlation/request IDs.
  • Make provider failures observable.
  • Keep runtime rendering deterministic.
  • Avoid outbound GitHub calls when sending.
  • Do not require a database for template resolution.
  • Keep API stateless.

Security expectations

  • No public/browser use of long-lived PostKit credentials.
  • Validate and normalise recipient inputs.
  • Sanitize/escape template variables according to the selected template engine rules.
  • Keep tenant secrets and provider credentials in Azure-supported secret/configuration mechanisms.
  • Prefer Managed Identity where practical.

Work

  • Bootstrap apps/api Azure Functions app.
  • Define typed send request/response contract.
  • Implement tenant authentication/resolution abstraction.
  • Implement template loading abstraction.
  • Implement rendering pipeline.
  • Implement provider abstraction.
  • Implement first provider integration.
  • Add validation and stable errors.
  • Add correlation IDs and structured logs.
  • Add unit/integration tests around tenant isolation and send flow.
  • Add local development configuration/documentation.
  • Add Azure deployment workflow following Platform Kit conventions.

Agent implementation notes

Before implementation, inspect the latest API/Azure Functions/deployment conventions in singleton-sd/poc-plattform-kit. Reuse its Node/TypeScript, configuration, testing, logging, and deployment patterns where appropriate.

Do not add a template database. Runtime templates come from the published template storage abstraction covered by the template publishing epic.

Acceptance criteria

  • A trusted consumer can send a template email through a typed HTTP endpoint.
  • Tenant identity is resolved from authentication and cannot be spoofed by request payload.
  • The API can load a tenant/environment-specific compiled template.
  • Variables are validated before provider invocation.
  • Provider failures and validation failures return stable typed errors.
  • Logs include correlation information without exposing secrets or full sensitive payloads.
  • The API is deployable as Azure Functions and is independently testable/buildable from the monorepo.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions