mirror of
https://github.com/hanzoai/notify.git
synced 2026-10-30 15:04:32 +00:00
Replaces the chi-based notifyd scaffold with the canonical
hanzoai/auto-style layout: hanzoai/base as the HTTP + SQLite + auth
substrate (via the platform plugin for IAM JWT validation and
X-Org-Id injection), hanzoai/tasks for durable async sends, and a
per-tenant Notifier resolver that pulls provider credentials from
KMS through platform.KMSClient.
Library at the module root stays untouched — all 33 provider impls
(plivo, twilio, ses, sendgrid, mail, whatsapp, discord, slack, …)
keep their existing public API. The service wraps them.
cmd/notifyd
- base.New + platform.MustRegister
- schema.MustRegister (6 collections: tenants, providers,
templates, events, messages, meter)
- tasks worker registered when TASKS_ADDR is set; sync path
runs the same Deliver activity directly when not
internal/
- boot/ thin platform.KMSClient wiring (env-driven)
- schema/ 6 base collections
- routes/ /v1/notify/{send,messages,providers,templates,events,metering,health}
+ convenience /send/{sms,email,voice,whatsapp}
- tenant/ KMS-backed resolver builds library Notifier per send
- template/ draft → pending → approved → published → archived
- event/ catalog routing (fills missing channel / template)
- metering/ per-send ledger writer + aggregate query
- tasks/ NotifySendWorkflow + Deliver activity (idempotent on
replay; reloads message row before resending)
- zaprpc/ procedure name constants (server impl follow-up)
pkg/
- client/ public HTTP SDK (Send / Message / Providers / Templates)
- types/ wire shapes shared by routes and client
cucloud: fmt.Errorf(msg) → errors.New(msg) for the go 1.26 vet pass
that landed with this dependency bump. Behaviour unchanged.
Boot smoke-tested locally:
- SQLite migrations apply cleanly
- /v1/notify/health returns {"service":"notify","status":"ok"}
- missing X-Org-Id returns 401 on every authenticated route
- send with stub tenant returns the expected base validation error
55 lines
1.9 KiB
Go
55 lines
1.9 KiB
Go
package tasks
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/hanzoai/tasks/pkg/sdk/workflow"
|
|
)
|
|
|
|
// SendInput is the workflow + activity input. One pair per recipient.
|
|
// MessageID is the notifyd-generated message row id the worker updates
|
|
// as the send progresses (queued → sending → sent/failed).
|
|
type SendInput struct {
|
|
MessageID string `json:"message_id"`
|
|
TenantSlug string `json:"tenant_slug"`
|
|
Channel string `json:"channel"`
|
|
Provider string `json:"provider,omitempty"`
|
|
To string `json:"to"`
|
|
Subject string `json:"subject,omitempty"`
|
|
Body string `json:"body"`
|
|
TemplateID string `json:"template_id,omitempty"`
|
|
Event string `json:"event,omitempty"`
|
|
Vars map[string]any `json:"vars,omitempty"`
|
|
}
|
|
|
|
// SendResult is the workflow return shape.
|
|
type SendResult struct {
|
|
MessageID string `json:"message_id"`
|
|
Status string `json:"status"`
|
|
Provider string `json:"provider"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// NotifySendWorkflow is the durable-execution entry point for async
|
|
// sends. The workflow body must be deterministic on its inputs — all
|
|
// I/O happens inside the Deliver activity.
|
|
//
|
|
// Single activity: this is intentional. A "send" is a unit of work,
|
|
// not a DAG; if a provider fails, retry the whole thing rather than
|
|
// trying to split rendering from delivery (rendering against the same
|
|
// vars yields the same body, so re-rendering on retry is free).
|
|
func NotifySendWorkflow(ctx workflow.Context, in SendInput) (SendResult, error) {
|
|
actCtx := workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
|
|
StartToCloseTimeout: 60 * time.Second,
|
|
})
|
|
var res SendResult
|
|
if err := workflow.ExecuteActivity(actCtx, "Deliver", in).Get(actCtx, &res); err != nil {
|
|
return SendResult{
|
|
MessageID: in.MessageID,
|
|
Status: "failed",
|
|
Error: err.Error(),
|
|
}, err
|
|
}
|
|
return res, nil
|
|
}
|