max / makenotwork
| 1 | -- A soft monthly ceiling on how much mail one creator sends. |
| 2 | -- |
| 3 | -- The commons being protected is the shared Postmark IP pool: one creator's |
| 4 | -- fan-out degrades delivery for every other creator on it. Nothing bounded that |
| 5 | -- before this. `db::lists::resolve_audience` caps one audience at 10,000 and |
| 6 | -- `users.last_broadcast_at` allows one broadcast per 24 hours; neither bounds |
| 7 | -- the count over a month, which is the number reputation actually follows. |
| 8 | -- |
| 9 | -- Soft, not hard, and the shape matters as much as the number: the creator can |
| 10 | -- see the count and the cap before meeting either, a send that would cross it is |
| 11 | -- refused with a message rather than silently swallowed, and an increase is |
| 12 | -- something they can ask for. A silent throttle reads as the platform losing |
| 13 | -- mail, which is worse than a refusal. |
| 14 | |
| 15 | -- One row per creator per billing period. The window is the creator's own |
| 16 | -- payment period rather than a calendar month, so the cap is legible next to |
| 17 | -- what they pay for; `period_start` is what makes the row, and it comes from |
| 18 | -- `creator_subscriptions.current_period_start` when there is one and from the |
| 19 | -- calendar month when there is not (an unsubscribed creator still sends). |
| 20 | -- |
| 21 | -- `sent_count` is reserved up front, per send, for the whole audience: the |
| 22 | -- decision has to be made before any mail leaves, and a per-mail increment would |
| 23 | -- be 8,000 writes for one announcement and would leave a half-mailed list when |
| 24 | -- it hit the cap mid-fan-out. |
| 25 | NOT EXISTS creator_mail_usage ( |
| 26 | user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, |
| 27 | period_start TIMESTAMPTZ NOT NULL, |
| 28 | period_end TIMESTAMPTZ NOT NULL, |
| 29 | sent_count BIGINT NOT NULL DEFAULT 0 CHECK (sent_count >= 0), |
| 30 | updated_at TIMESTAMPTZ NOT NULL DEFAULT now, |
| 31 | PRIMARY KEY (user_id, period_start) |
| 32 | ); |
| 33 | |
| 34 | -- The per-account override, above whatever the creator's tier defaults to. |
| 35 | -- NULL means "the tier default applies", which is every account until an |
| 36 | -- operator grants an increase. Nullable rather than defaulted to the tier |
| 37 | -- number: a default would freeze today's number onto the row and stop a config |
| 38 | -- change from reaching accounts that never asked for anything. |
| 39 | users |
| 40 | ADD COLUMN monthly_mail_cap_override INTEGER |
| 41 | CHECK (monthly_mail_cap_override IS NULL OR monthly_mail_cap_override > 0); |
| 42 | |
| 43 | -- The application path. A creator asks for a number and says why; the |
| 44 | -- explanation is the point, since it is what separates a growing list from a |
| 45 | -- stale one being blasted. |
| 46 | -- |
| 47 | -- `status` is a closed set enforced app-side ('pending', 'granted', 'denied'), |
| 48 | -- stored as TEXT for the same reason `admin_alerts` does it: extending the set |
| 49 | -- should be a code change and not a migration. |
| 50 | NOT EXISTS mail_cap_requests ( |
| 51 | id UUID PRIMARY KEY DEFAULT gen_random_uuid, |
| 52 | user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, |
| 53 | requested_cap INTEGER NOT NULL CHECK (requested_cap > 0), |
| 54 | reason TEXT NOT NULL, |
| 55 | status TEXT NOT NULL DEFAULT 'pending', |
| 56 | granted_cap INTEGER CHECK (granted_cap IS NULL OR granted_cap > 0), |
| 57 | created_at TIMESTAMPTZ NOT NULL DEFAULT now, |
| 58 | decided_at TIMESTAMPTZ, |
| 59 | decided_by UUID REFERENCES users(id) ON DELETE SET NULL |
| 60 | ); |
| 61 | |
| 62 | -- The operator queue is "pending, oldest first"; the creator's own view is |
| 63 | -- "mine, newest first". One index serves the first, the PK-less user lookup the |
| 64 | -- second. |
| 65 | NOT EXISTS idx_mail_cap_requests_pending |
| 66 | ON mail_cap_requests (created_at) WHERE status = 'pending'; |
| 67 | NOT EXISTS idx_mail_cap_requests_user |
| 68 | ON mail_cap_requests (user_id, created_at DESC); |
| 69 | |
| 70 | -- One open request at a time. A creator who asks twice is amending their ask, |
| 71 | -- not queueing a second one, and an operator granting one of two identical |
| 72 | -- pending rows leaves the other to be granted again later. |
| 73 | NOT EXISTS idx_mail_cap_requests_one_open |
| 74 | ON mail_cap_requests (user_id) WHERE status = 'pending'; |
| 75 |