AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Notifications

skill-tomaspozo-agentlink-notifications · by tomaspozo

Transactional and notification emails (welcome, "your export is ready", "payment failed", receipts, alerts) on Supabase — everything that is NOT a Supabase Auth email. Use when the task involves sending an app-driven email, adding a new email template, firing email from a database event or trigger, or debugging delivery of a non-auth email. Activate whenever the task touches api._admin_send_email…

No reviews yet
0 installs
9 views
0.0% view→install

Install

$ agentstack add skill-tomaspozo-agentlink-notifications

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-tomaspozo-agentlink-notifications)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Notifications? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Notifications (Transactional Email)

App-driven emails — welcome, "your export is ready", "payment failed", receipts, digests, alerts. Anything fired by your code or a DB event, as opposed to Supabase Auth's own emails (those go through the auth skill's email hook).

The infrastructure already exists in every scaffolded project. You do not build a queue, a worker, a cron, or a retry loop — you register a template and call one RPC.

The one rule: send from the server, never the client

Transactional email is service-role only. The entry point — api._admin_send_email(...) — is SECURITY DEFINER, granted to service_role, and revoked from anon/authenticated. Fire it from a DB trigger, another SECURITY DEFINER function, or a secret-auth edge function. Never expose an RPC that lets a client send arbitrary email (recipient + content) — that is a spam and spoofing vector. The events that need email (signup, export done, payment failed) originate server-side anyway.

How it flows

server-side event ─→ api._admin_send_email('', recipient, params, dedupe_key)
   │   inserts public.internal_logs_email (status 'queued', unique dedupe_key)
   │   enqueues api._admin_enqueue_task('internal-send-email', { log_id })
   ▼
internal-queue-worker  (existing, generic) ──invoke──▶ internal-send-email
   │   loads the log row (api._admin_email_log_get)
   │   if status='sent' → skip (idempotent);  else: TEMPLATES[email_id] → render → Resend.send
   ▼
api._admin_email_log_mark(log_id, 'sent'|'failed', resend_id, error)

This reuses the same queue + worker that powers auth emails and workspace invites. PGMQ's visibility-timeout + read_ct already give you retry-with-backoff and a dead-letter (archive) after QUEUE_MAX_RETRIES — see the [edge-functions](../edge-functions/SKILL.md) and [database](../database/SKILL.md) skills for the queue/worker model. The 1-minute cron is only a safety net; api._admin_send_email fires the worker immediately via pg_net, so latency is seconds, not a minute.

Send an email

-- From any SECURITY DEFINER function or trigger (service_role context):
PERFORM api._admin_send_email(
  'export_ready',                                   -- email_id (template key)
  v_user_email,                                     -- recipient
  jsonb_build_object('download_url', v_url),        -- params → template props
  'export_ready:' || v_export_id::text              -- dedupe_key (optional, recommended)
);
  • dedupe_key is your idempotency guarantee. A partial unique index on

internal_logs_email.dedupe_key means one row — and therefore one email — per key, even if the trigger fires twice. Use a stable id (:). Omit it only for genuinely fire-and-forget sends.

  • Params are untrusted jsonb. The template's render() narrows them — never

assume a shape. Keep params small and serializable.

Add a new transactional email

Three steps, all mirroring the shipped welcome example:

  1. Template — create supabase/functions/internal-send-email/_templates/.tsx,

a React Email component that composes the shared chrome from supabase/functions/_shared/email-components/ (EmailLayout, EmailButton, typography). Match the existing templates' structure.

  1. Register — add an entry to the TEMPLATES registry in

supabase/functions/internal-send-email/index.ts, keyed by your email_id, with a subject(params, appName) and a render(params, appName) that validates params and returns the React element.

  1. Fire — call api._admin_send_email('', recipient, params, dedupe_key)

from the server-side event that should trigger it (a trigger, an _internal_admin_* function, or a secret-auth edge function).

Then pnpm exec agentlink db apply (for any new SQL) and deploy the function.

The sample: welcome email

The scaffold ships one working example so the path is live on day one: public._internal_welcome_on_profile() is fired by an AFTER INSERT trigger on public.profiles and enqueues the welcome template. It is intentionally routed through this queue (NOT the auth email hook), so it never collides with the signup confirmation email. Customize the copy in _templates/welcome.tsx, or remove the trg_profiles_welcome_email trigger if you don't want it.

> Timing nuance: a profile is created at signup, before email confirmation — > so the welcome arrives alongside the confirmation email. To send it only after the > user confirms, move the trigger to AFTER UPDATE OF email_confirmed_at ON auth.users. > See [transactional-email.md](references/transactional-email.md) for the full recipe.

Migrating projects scaffolded before the unified path (internal-invite-member)

Older AgentLink projects shipped a dedicated internal-invite-member edge function for workspace invites (enqueued directly from _internal_admin_create_invitation / _internal_admin_resend_invitation). New scaffolds fold that into this path — the invite entry in internal-send-email's registry — so all app email goes through one function.

If you're in an older project and the user wants the consolidated setup, recommend migrating, but confirm with the user first — it removes an edge function and (on cloud) leaves an orphaned deployment to delete. Steps:

  1. Add an invite entry to internal-send-email's TEMPLATES registry (subject + render building the /accept-invite?token=… URL from { token, tenant_name }); move internal-invite-member/_templates/team-invite.tsx to internal-send-email/_templates/.
  2. Repoint public._internal_admin_create_invitation and _internal_admin_resend_invitation to api._admin_send_email('invite', email, jsonb_build_object('token', …, 'tenant_name', …)). Pass no dedupe_key — resending an invitation must deliver a fresh email.
  3. Delete supabase/functions/internal-invite-member/ and remove its [functions.internal-invite-member] block from supabase/config.toml.
  4. pnpm exec agentlink db apply, then db migrate and deploy.
  5. Cloud only: delete the now-orphaned deployed function — supabase functions delete internal-invite-member.

The same shape applies to any other bespoke per-email function (e.g. an app's internal-approval-decision): move its template into the registry, repoint its enqueue to api._admin_send_email, delete the function. Auth emails are the exception — they stay on the auth hook.

Deeper reference

  • [references/transactional-email.md](references/transactional-email.md) — full

add-an-email recipe, the internal_logs_* log convention, idempotency/retry semantics, local testing with resend-box, and troubleshooting.

  • [references/resend-box.md](references/resend-box.md) — the local email

sandbox's HTTP API (list/filter/fetch/clear captured emails) and a verification recipe for confirming a template actually rendered right, not just that internal_logs_email says sent.

  • Auth emails (signup, magic link, recovery, email change): [auth skill](../auth/SKILL.md).
  • Per-environment Resend setup (API key, FROM address, local vs cloud):

[cli/references/resend.md](../cli/references/resend.md).

  • Queue + worker + cron model: [edge-functions](../edge-functions/SKILL.md),

[database](../database/SKILL.md), and worked examples in [recipes.md](../../agents/references/recipes.md).

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.