supabase-brand-studio-notifications
Exact checked-in database declaration: docs/supabase-brand-studio-notifications.sql
Source: docs/supabase-brand-studio-notifications.sql. Declared objects: function:brand_studio_notification_claim, function:brand_studio_notification_enqueue, function:brand_studio_notification_finish, function:brand_studio_notification_preference, function:brand_studio_notification_queue_claim, function:brand_studio_notification_queue_finish, function:brand_studio_notification_unsubscribe, index:brand_studio_notification_due_idx, index:brand_studio_notification_queue_due_idx, table:brand_studio_notification_outbox, table:brand_studio_notification_preferences, table:brand_studio_notification_suppressions. This is repository DDL, not a claim about live deployment.
All database declarations · Data structures and lineage
-- Prepared only: parent applies separately. Email/payload are private service-role data.
create table if not exists public.brand_studio_notification_preferences (
user_id uuid primary key references auth.users(id) on delete cascade,
recipient text not null,
enabled boolean not null default false,
version uuid not null default gen_random_uuid(),
suppression_reason text,
updated_at timestamptz not null default now()
);
create table if not exists public.brand_studio_notification_suppressions (
recipient text primary key, reason text not null, suppressed_at timestamptz not null default now()
);
create table if not exists public.brand_studio_notification_outbox (
id uuid primary key,
user_id uuid not null references auth.users(id) on delete cascade,
preference_version uuid not null,
recipient text not null,
status text not null check (status in ('pending','sending','retry','unknown','accepted','delivered','suppressed','failed')),
idempotency_key text not null unique,
payload jsonb not null,
payload_sha256 text not null check (payload_sha256 ~ '^[a-f0-9]{64}$'),
provider_id text,
reason text,
created_at timestamptz not null default now(),
first_attempt_at timestamptz,
accepted_at timestamptz,
delivered_at timestamptz,
attempts integer not null default 0,
lease_token uuid,
lease_until timestamptz,
next_attempt_at timestamptz not null default now()
);
alter table public.brand_studio_notification_outbox drop constraint if exists brand_studio_notification_outbox_status_check;
alter table public.brand_studio_notification_outbox add constraint brand_studio_notification_outbox_status_check
check (status in ('pending','sending','retry','unknown','accepted','delivered','suppressed','failed'));
alter table public.brand_studio_notification_preferences
add column if not exists next_queue_at timestamptz not null default now(),
add column if not exists queue_lease uuid,
add column if not exists queue_lease_until timestamptz;
create index if not exists brand_studio_notification_queue_due_idx on public.brand_studio_notification_preferences(next_queue_at,user_id) where enabled and suppression_reason is null;
create index if not exists brand_studio_notification_due_idx on public.brand_studio_notification_outbox(next_attempt_at) where status in ('pending','retry','sending','accepted','delivered');
alter table public.brand_studio_notification_suppressions enable row level security;
revoke all on public.brand_studio_notification_suppressions from public, anon, authenticated;
grant select on public.brand_studio_notification_suppressions to service_role;
insert into public.brand_studio_notification_suppressions(recipient,reason)
select lower(recipient),suppression_reason from public.brand_studio_notification_preferences where suppression_reason is not null on conflict do nothing;
alter table public.brand_studio_notification_preferences enable row level security;
alter table public.brand_studio_notification_outbox enable row level security;
-- No client policies: routes enforce session owner and return redacted projections.
revoke all on public.brand_studio_notification_preferences, public.brand_studio_notification_outbox from public, anon, authenticated;
grant all on public.brand_studio_notification_preferences, public.brand_studio_notification_outbox to service_role;
create or replace function public.brand_studio_notification_preference(p_user_id uuid, p_email text, p_enabled boolean)
returns setof public.brand_studio_notification_preferences language plpgsql security definer set search_path = public, pg_temp as $$
begin
if not exists (select 1 from auth.users where id=p_user_id and email=p_email and email_confirmed_at is not null) then raise exception 'verified_email_required'; end if;
insert into public.brand_studio_notification_preferences(user_id, recipient, enabled) values (p_user_id,p_email,false) on conflict do nothing;
perform 1 from public.brand_studio_notification_preferences where user_id=p_user_id for update;
if p_enabled and (exists (select 1 from public.brand_studio_notification_suppressions where recipient=lower(p_email)) or exists (select 1 from public.brand_studio_notification_preferences where user_id=p_user_id and suppression_reason is not null and recipient=p_email)) then raise exception 'provider_suppression_requires_review'; end if;
return query update public.brand_studio_notification_preferences
set recipient=p_email, enabled=p_enabled,
version=case when recipient is distinct from p_email or enabled is distinct from p_enabled then gen_random_uuid() else version end,
updated_at=case when recipient is distinct from p_email or enabled is distinct from p_enabled then now() else updated_at end,
next_queue_at=case when recipient is distinct from p_email or enabled is distinct from p_enabled then now() else next_queue_at end,
queue_lease=case when recipient is distinct from p_email or enabled is distinct from p_enabled then null else queue_lease end,
queue_lease_until=case when recipient is distinct from p_email or enabled is distinct from p_enabled then null else queue_lease_until end,
suppression_reason=case when recipient<>p_email then null else suppression_reason end
where user_id=p_user_id returning *;
if not p_enabled then
update public.brand_studio_notification_outbox set status='suppressed',reason='consent_not_current',lease_token=null,lease_until=null
where user_id=p_user_id and provider_id is null and status in ('pending','retry');
end if;
end $$;
create or replace function public.brand_studio_notification_queue_claim(p_limit integer)
returns setof public.brand_studio_notification_preferences language plpgsql security definer set search_path = public, pg_temp as $$
begin
if p_limit is null or p_limit<1 or p_limit>2 then raise exception 'bounded_batch_required'; end if;
return query with due as (
select p.user_id from public.brand_studio_notification_preferences p
join auth.users u on u.id=p.user_id and u.email=p.recipient and u.email_confirmed_at is not null
where p.enabled and p.suppression_reason is null and p.next_queue_at<=now() and (p.queue_lease_until is null or p.queue_lease_until<now())
order by p.next_queue_at,p.user_id for update of p skip locked limit p_limit
) update public.brand_studio_notification_preferences p set queue_lease=gen_random_uuid(),queue_lease_until=now()+interval '2 minutes'
from due where p.user_id=due.user_id returning p.*;
end $$;
create or replace function public.brand_studio_notification_queue_finish(p_user_id uuid,p_version uuid,p_lease uuid,p_queued boolean)
returns boolean language plpgsql security definer set search_path = public, pg_temp as $$
begin
update public.brand_studio_notification_preferences set queue_lease=null,queue_lease_until=null,
next_queue_at=case when p_queued then now()+interval '7 days' else now()+interval '5 minutes' end
where user_id=p_user_id and version=p_version and queue_lease=p_lease;
return found;
end $$;
create or replace function public.brand_studio_notification_unsubscribe(p_user_id uuid,p_version uuid)
returns boolean language plpgsql security definer set search_path = public, pg_temp as $$
begin
-- Old messages may only disable their own consent version, never later re-opt-in.
update public.brand_studio_notification_preferences set enabled=false,updated_at=now() where user_id=p_user_id and version=p_version;
update public.brand_studio_notification_outbox set status='suppressed',reason='consent_not_current',lease_token=null,lease_until=null
where user_id=p_user_id and preference_version=p_version and provider_id is null and status in ('pending','retry');
return true;
end $$;
create or replace function public.brand_studio_notification_enqueue(p_entry jsonb)
returns boolean language plpgsql security definer set search_path = public, pg_temp as $$
declare pref public.brand_studio_notification_preferences;
begin
select * into pref from public.brand_studio_notification_preferences where user_id=(p_entry->>'user_id')::uuid for update;
if not found or not pref.enabled or pref.suppression_reason is not null or pref.version<>(p_entry->>'preference_version')::uuid or pref.recipient<>p_entry->>'recipient' then raise exception 'consent_not_current'; end if;
insert into public.brand_studio_notification_outbox(id,user_id,preference_version,recipient,status,idempotency_key,payload,payload_sha256,created_at)
values ((p_entry->>'id')::uuid,pref.user_id,pref.version,pref.recipient,'pending',p_entry->>'idempotency_key',p_entry->'payload',p_entry->>'payload_sha256',now()) on conflict (id) do nothing;
return true;
end $$;
create or replace function public.brand_studio_notification_claim(p_limit integer)
returns setof public.brand_studio_notification_outbox language plpgsql security definer set search_path = public, pg_temp as $$
begin
if p_limit is null or p_limit<1 or p_limit>2 then raise exception 'bounded_batch_required'; end if;
update public.brand_studio_notification_outbox set status='unknown',reason='idempotency_or_attempt_limit_requires_reconciliation',lease_token=null,lease_until=null
where provider_id is null and status in ('pending','retry','sending') and (lease_until is null or lease_until<now())
and (first_attempt_at<=now()-interval '23 hours' or attempts>=5);
update public.brand_studio_notification_outbox set status='unknown',reason='delivery_status_requires_reconciliation',lease_token=null,lease_until=null
where provider_id is not null and delivered_at is null and status in ('accepted','sending') and accepted_at<now()-interval '7 days' and (lease_until is null or lease_until<now());
return query with due as (
select id from public.brand_studio_notification_outbox
where status in ('pending','retry','sending','accepted','delivered') and next_attempt_at<=now() and (lease_until is null or lease_until<now())
and (delivered_at is null or delivered_at>=now()-interval '30 days')
order by case when provider_id is null then 0 when delivered_at is null then 1 else 2 end,next_attempt_at,id for update skip locked limit p_limit
) update public.brand_studio_notification_outbox o set status='sending',lease_token=gen_random_uuid(),lease_until=now()+interval '2 minutes',
first_attempt_at=case when o.provider_id is null then coalesce(o.first_attempt_at,now()) else o.first_attempt_at end,
attempts=case when o.provider_id is null then o.attempts+1 else o.attempts end
from due where o.id=due.id returning o.*;
end $$;
create or replace function public.brand_studio_notification_finish(p_id uuid,p_lease uuid,p_status text,p_reason text,p_provider_id text)
returns boolean language plpgsql security definer set search_path = public, pg_temp as $$
declare saved public.brand_studio_notification_outbox;
begin
if p_status not in ('retry','unknown','accepted','delivered','suppressed','failed') or length(p_reason)>100 then raise exception 'invalid_notification_result'; end if;
select * into saved from public.brand_studio_notification_outbox where id=p_id and lease_token=p_lease and status='sending' for update;
if not found then return false; end if;
if p_status in ('accepted','delivered') and p_provider_id is null then raise exception 'provider_identity_required'; end if;
update public.brand_studio_notification_outbox set status=p_status,reason=p_reason,provider_id=coalesce(saved.provider_id,p_provider_id),lease_token=null,lease_until=null,
accepted_at=case when p_status in ('accepted','delivered') then coalesce(saved.accepted_at,now()) else saved.accepted_at end,
delivered_at=case when p_status='delivered' then coalesce(saved.delivered_at,now()) else saved.delivered_at end,
next_attempt_at=now()+case when p_status='delivered' then interval '1 day' when p_status='accepted' then interval '1 hour' else interval '5 minutes' end where id=p_id;
if p_status in ('suppressed','failed') and p_reason in ('provider_bounced','provider_complained','provider_failed','provider_suppressed') then
insert into public.brand_studio_notification_suppressions(recipient,reason) values(lower(saved.recipient),p_reason) on conflict(recipient) do update set reason=excluded.reason,suppressed_at=now();
update public.brand_studio_notification_preferences set enabled=false,suppression_reason=p_reason,updated_at=now() where lower(recipient)=lower(saved.recipient);
end if;
return true;
end $$;
revoke all on function public.brand_studio_notification_preference(uuid,text,boolean),public.brand_studio_notification_unsubscribe(uuid,uuid),public.brand_studio_notification_enqueue(jsonb),public.brand_studio_notification_claim(integer),public.brand_studio_notification_finish(uuid,uuid,text,text,text) from public,anon,authenticated;
grant execute on function public.brand_studio_notification_preference(uuid,text,boolean),public.brand_studio_notification_unsubscribe(uuid,uuid),public.brand_studio_notification_enqueue(jsonb),public.brand_studio_notification_claim(integer),public.brand_studio_notification_finish(uuid,uuid,text,text,text) to service_role;
revoke all on function public.brand_studio_notification_queue_claim(integer),public.brand_studio_notification_queue_finish(uuid,uuid,uuid,boolean) from public,anon,authenticated;
grant execute on function public.brand_studio_notification_queue_claim(integer),public.brand_studio_notification_queue_finish(uuid,uuid,uuid,boolean) to service_role;