Problem
In a workspace with multiple roles (developer, admin, owner), there is no gate between drafting a broadcast and firing it to every subscriber. Today any role with CapBroadcast can send instantly. For real teams that is unacceptable: marketers should be able to compose without the power to mass-send, and owners should sign off before anything goes out.
Proposal
Add a status machine to broadcasts so the team can split who composes from who approves:
draft → in_review → approved → sending → sent
↘ rejected (back to draft)
Schema (broadcasts already have a status column for "sending"; extend it):
ALTER TABLE campaigns ADD COLUMN approval_status VARCHAR(16); -- draft | in_review | approved | rejected
ALTER TABLE campaigns ADD COLUMN submitted_by UUID REFERENCES users(id);
ALTER TABLE campaigns ADD COLUMN reviewed_by UUID REFERENCES users(id);
ALTER TABLE campaigns ADD COLUMN reviewed_at TIMESTAMPTZ;
ALTER TABLE campaigns ADD COLUMN review_comment TEXT;
New endpoints:
POST /campaigns/{id}/submit — sender = composer, sets in_review. Required for non-owner/admin roles before send.
POST /campaigns/{id}/approve — only owner/admin, sets approved, captures reviewer + timestamp + optional comment. Triggers the same dispatch path that the current Send button uses.
POST /campaigns/{id}/reject — only owner/admin, sets rejected with a comment. Composer can edit and resubmit.
Capability tweak:
developer role: CapCampaignsWrite (already), but not the new CapCampaignsApprove.
admin and owner: both caps.
- Workspaces on free / Pro tier: feature absent (no approval flow, the existing direct-send path stays as-is).
UI:
- Campaigns list shows status badges: Draft, In review, Approved, Rejected.
- Detail view: composer sees "Submit for review" instead of "Send"; admin/owner see "Approve" + "Reject" with comment field when status is
in_review.
- A small inbox-style indicator in the dashboard for pending reviews, scoped to workspaces where you have approval rights.
Acceptance criteria
- A developer cannot send a campaign directly; the only available action when status is
draft is "Submit for review".
- An admin/owner approving a campaign moves it to the existing send pipeline; nothing about scheduling or per-subscriber dispatch changes.
- Audit log records
campaign.submit, campaign.approve, campaign.reject with reviewer info.
- The flow is gated behind the Team plan: workspaces without a Team license keep the current behavior (direct send for any role with
CapCampaignsWrite).
Why Team-tier
This is the feature that turns "a workspace with roles" into "a team that can actually operate safely". Without it, the developer/admin/owner split is just labels; with it, the workflow physically prevents a junior from accidentally blasting 50k addresses. That separation is what justifies the upgrade above Pro.
Problem
In a workspace with multiple roles (developer, admin, owner), there is no gate between drafting a broadcast and firing it to every subscriber. Today any role with
CapBroadcastcan send instantly. For real teams that is unacceptable: marketers should be able to compose without the power to mass-send, and owners should sign off before anything goes out.Proposal
Add a status machine to broadcasts so the team can split who composes from who approves:
Schema (broadcasts already have a status column for "sending"; extend it):
New endpoints:
POST /campaigns/{id}/submit— sender = composer, setsin_review. Required for non-owner/admin roles before send.POST /campaigns/{id}/approve— only owner/admin, setsapproved, captures reviewer + timestamp + optional comment. Triggers the same dispatch path that the currentSendbutton uses.POST /campaigns/{id}/reject— only owner/admin, setsrejectedwith a comment. Composer can edit and resubmit.Capability tweak:
developerrole:CapCampaignsWrite(already), but not the newCapCampaignsApprove.adminandowner: both caps.UI:
in_review.Acceptance criteria
draftis "Submit for review".campaign.submit,campaign.approve,campaign.rejectwith reviewer info.CapCampaignsWrite).Why Team-tier
This is the feature that turns "a workspace with roles" into "a team that can actually operate safely". Without it, the developer/admin/owner split is just labels; with it, the workflow physically prevents a junior from accidentally blasting 50k addresses. That separation is what justifies the upgrade above Pro.