notify

package
v1.801.469 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package notify is transactional email and SMS, sent through your org's own provider credential.

POST /v1/notify/send delivers one message by email or SMS through the caller org's OWN KMS-held credential, and notify.Send (send.go) is that same rail for in-process callers — one sender, never a second provider path.

It is the native replacement for the standalone notifyd (github.com/hanzoai/notify) Deployment.

SCOPE — the one live contract. notifyd's ONLY production consumer is Hanzo IAM, which POSTs OTP sends to POST /v1/notify/send?sync=true with the wire body

{"to":["…"],"channel":"sms|email","event":"iam.otp_sent",
 "template_vars":{"otp":"…","recipient":"…","app":"…"}}

and treats a response status of "sent"/"delivered" as success (see hanzoai/iam object/notify_delivery_http.go). This subsystem serves that exact contract natively. Everything else notifyd carries — the tenants/templates/ providers/preferences/unsubscribe/metering/events/messages collections and the hanzoai/tasks (Temporal) async worker — has NO live consumer (the live tenant's template/provider/event tables are empty and only IAM calls /send), so it is deliberately NOT folded. The Temporal notify-send queue plane is owned elsewhere and is not touched here; async sends (no ?sync=true) return 503, exactly as notifyd does when it runs without a connected worker.

DRY — no reimplementation of provider plumbing. The actual provider implementations and the wire structs are notifyd's OWN public packages, imported directly: github.com/hanzoai/notify/service/{twilio,twilioemail,plivo,mail} and github.com/hanzoai/notify/pkg/types. Only the thin credential→constructor glue (constructProvider) — which lives in notifyd's internal/ and is therefore not importable across the module boundary — is mirrored here, matching internal/tenant/tenant.go verbatim.

SECURITY — the trust boundary moves with the code. notifyd was ClusterIP-internal and trusted a raw X-Org-Id header. Mounted here, /v1/notify/send is reachable via the public gateway (api.hanzo.ai forwards every path to cloud), so it gates on a VALIDATED principal and derives the org from principal.Org (the identity middleware's trusted, gateway-minted X-Org-Id), never from a client-supplied header. An unauthenticated caller gets 401; a signed-in caller can only send scoped to their OWN org.

CREDENTIALS — KMS only, never env, never plaintext, never logged. Provider credentials are read EXCLUSIVELY from cloud's embedded KMS via cloud.Deps.KMS, at the org-scoped, rotatable ref orgs/<org>/notify/<service>/<key> — the SAME /orgs/<org> namespace apps/integrations uses, so a cred is writable and rotatable through POST /v1/kms/orgs/:org/secrets with a validated org token (no operator-injected env Secret, no restart to rotate). The org is the VALIDATED principal's tenant (never a client header). A missing key yields an empty value and constructProvider fails closed; no secret is ever hard-coded, read from the environment, or logged.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount registers the native /v1/notify/* send surface on app.

func Send

func Send(ctx context.Context, kms cloud.KMSClient, org, channel, provider string, to []string, subject, body string) (usedProvider string, err error)

Send delivers ONE message through the notify rail in-process, resolving the provider from the org's KMS credentials and constructing the notifyd provider exactly as POST /v1/notify/send?sync=true does. It IS the one platform sender: the HTTP handler (handleSend) and every in-process caller (e.g. the marketing drip engine sending sequence steps) funnel through the SAME sendReal delivery path, so there is never a second sender and never a divergence in how a provider is chosen or built.

The caller supplies the KMS client (cloud.Deps.KMS / cloud.Base.KMS) and the VALIDATED org — a background task carries no request principal, so the org is the task's owner-scoped value, never a client header. provider "" selects the org's configured default for the channel; channel is "email" or "sms". It returns the provider service it used (for the caller's audit log) and any delivery error. A missing credential fails closed inside constructProvider.

Send performs NO suppression/opt-out check — that policy is the caller's, so transactional sends (IAM OTP) are never suppressed while marketing sends pass through the marketing suppression gate before reaching here. One sender, one place; the opt-out decision stays with the sender that owns the audience.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL