Skip to main content

max / makenotwork

Email infrastructure, bounce handling, UX improvements, and pre-deploy fixes - Postmark bounce/complaint webhook with email suppression table - Email unsubscribe system: signed URLs, List-Unsubscribe headers (RFC 8058), one-click POST handler, per-notification opt-out - Release announcement opt-in (notify_release preference, filtered follower query) - Login notification toggle on preferences form - Creator-only notification prefs (sale/follower hidden for non-creators) - Broadcast confirmation with follower count - Password form clears on successful change - Copy button shows "Copied!" feedback - Email verification nudge on Account tab - SyncKit blob storage, app linking, device management - Discount code project scoping, Stripe webhook v2 support - Stripe Connect flow refactor (oauth.rs → connect.rs) - Admin CLI tool (mnw-admin) - Fix promotions tab test (tab removed, test updated) - Doc updates (roadmap, FAQ, terms, infrastructure) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-03-09 00:17 UTC
Commit: cba62a077006126961ad7534bf1cd280a0236641
Parent: 8b4f346
99 files changed, +3512 insertions, -785 deletions
@@ -6,7 +6,7 @@ Binding commitments from Makenot.work to every creator on the platform. These ar
6 6
7 7 ## Revenue
8 8
9 - **Guarantee:** 100% of fan payments reach the creator, minus only the payment processor's fee (currently Stripe, ~3%).
9 + **Guarantee:** 0% platform fee on fan payments. The only deduction is the payment processor's fee (currently Stripe, ~3%).
10 10
11 11 - No platform percentage cut, ever.
12 12 - No transaction fees, payout fees, or skimming.
@@ -10,7 +10,7 @@ Flat fee. All your revenue passes through to you.
10 10 2. **Upload content** — audio, text, or digital files
11 11 3. **Organize** using hierarchical tags and projects
12 12 4. **Set pricing** — free, pay-what-you-want, fixed price, or subscription
13 - 5. **Get paid** — 100% of fan payments go to you
13 + 5. **Get paid** — 0% platform fee, only Stripe processing
14 14
15 15 ## For Fans
16 16
@@ -40,7 +40,7 @@ We never touch creator revenue. Payments go directly to creator-controlled Strip
40 40
41 41 ## The Model
42 42
43 - You pay a monthly subscription based on content type ($10-40). Your fan revenue is yours — 100% minus payment processing (~3% from Stripe).
43 + You pay a monthly subscription based on content type ($10-40). We take 0% of your fan revenue — the only deduction is payment processing (~3% from Stripe).
44 44
45 45 We're funded by subscriptions, not by your success. No ads, no percentage cuts, no hidden fees.
46 46
@@ -92,7 +92,7 @@ Video and streaming cost more than audio. Audio costs more than text. The prices
92 92 - Project organization (`/p/projectname`)
93 93 - Permanent content links (`/i/UUID`)
94 94 - Complete data export (projects, items, blog posts, transactions)
95 - - Direct fan payments (you keep 100%)
95 + - Direct fan payments (0% platform fee)
96 96 - Contact sharing with fans (opt-in at purchase)
97 97 - RSS feed generation (project + blog feeds)
98 98 - License keys, discount codes, download codes
@@ -52,7 +52,7 @@ Everything listed here is live and working.
52 52
53 53 ### Payments & Stripe
54 54
55 - - **100% revenue passthrough**: Payments go directly to creator-controlled Stripe accounts
55 + - **0% platform fee**: Payments go directly to creator-controlled Stripe accounts
56 56 - **Stripe Connect onboarding**: Guided setup, connected account status tracking
57 57 - **Webhook processing**: Checkout completion, refunds, subscription lifecycle (active, past due, canceled), renewal billing
58 58 - **Atomic checkout**: Row-level locking prevents double-purchases and race conditions
@@ -72,6 +72,7 @@ Everything listed here is live and working.
72 72
73 73 - **Source-available codebase**: PolyForm Noncommercial 1.0.0
74 74 - **Creator waitlist**: Invite-only launch with lottery waves and hand-picked approvals
75 + - **Admin CLI** (`mnw-admin`): Command-line tool for waitlist management, creator approval, spam flagging, wave execution, and stats -- connects directly to the database, no web UI needed
75 76 - **Documentation**: Server-rendered from markdown, auto-linked cross-references
76 77 - **Health monitoring**: Real uptime tracking, database status, service connectivity checks
77 78 - **386 automated tests**: Unit, integration, workflow, and health tests
@@ -98,6 +99,7 @@ Near-term work. No timelines because we ship when it's ready.
98 99 - **Sale and follower notifications** (email alerts for creators)
99 100 - **Contacts dashboard** (view fans who shared their email at purchase)
100 101 - **Malware scanning** on upload (ClamAV + VirusTotal hash lookup)
102 + - **Admin CLI expansion**: User suspension/unsuspension, appeal processing, broadcast sending, revenue/transaction reports, data export triggers, storage usage audit
101 103
102 104 ---
103 105
@@ -29,7 +29,7 @@
29 29 <li><strong>Upload content</strong> -- audio, video, text, or streams</li>
30 30 <li><strong>Organize</strong> using hierarchical tags and projects</li>
31 31 <li><strong>Set pricing</strong> -- free, pay-what-you-want, fixed price, or subscription</li>
32 - <li><strong>Get paid</strong> -- 100% of fan payments go to you</li>
32 + <li><strong>Get paid</strong> -- 0% platform fee, only Stripe processing</li>
33 33 </ol>
34 34
35 35 <h2>For Fans</h2>
@@ -60,7 +60,7 @@ Creator withdraws on their schedule</div>
60 60
61 61 <h2>The Model</h2>
62 62
63 - <p>You pay a monthly subscription based on content type ($10-40). Your fan revenue is yours -- 100% minus payment processing (~3% from Stripe).</p>
63 + <p>You pay a monthly subscription based on content type ($10-40). We take 0% of your fan revenue -- the only deduction is payment processing (~3% from Stripe).</p>
64 64
65 65 <p>We're funded by subscriptions, not by your success. No ads, no percentage cuts, no hidden fees.</p>
66 66
@@ -166,7 +166,7 @@ Creator withdraws on their schedule</div>
166 166 <li>Permanent content links (<code>/i/UUID</code>)</li>
167 167 <li>Sales and play count tracking</li>
168 168 <li>Project and transaction data export</li>
169 - <li>Direct fan payments (you keep 100%)</li>
169 + <li>Direct fan payments (0% platform fee)</li>
170 170 <li>Contact sharing (opt-in at purchase)</li>
171 171 <li>RSS feed generation</li>
172 172 <li>License keys, discount codes, download codes</li>
@@ -26,11 +26,11 @@
26 26
27 27 <div class="faq-item">
28 28 <h3>What is Makenot.work?</h3>
29 - <p>A platform for creators to sell content directly to fans. You keep 100% of revenue. We charge a flat subscription.</p>
29 + <p>A platform for creators to sell content directly to fans. 0% platform fee -- only Stripe's ~3% processing applies. We charge a flat subscription.</p>
30 30 </div>
31 31
32 32 <div class="faq-item">
33 - <h3>How do you make money if creators keep 100%?</h3>
33 + <h3>How do you make money with 0% platform fee?</h3>
34 34 <p>Creators pay a monthly subscription ($10-$40 based on content type). We don't take a cut of sales.</p>
35 35 </div>
36 36
@@ -24,7 +24,7 @@
24 24
25 25 <section class="sla-guarantee">
26 26 <h2>Revenue</h2>
27 - <p class="sla-statement"><strong>Guarantee:</strong> 100% of fan payments reach the creator, minus only the payment processor's fee (currently Stripe, ~3%).</p>
27 + <p class="sla-statement"><strong>Guarantee:</strong> 0% platform fee on fan payments. The only deduction is the payment processor's fee (currently Stripe, ~3%).</p>
28 28 <ul>
29 29 <li>No platform percentage cut, ever.</li>
30 30 <li>No transaction fees, payout fees, or skimming.</li>
@@ -30,7 +30,7 @@
30 30 <li>Audio uploads and streaming</li>
31 31 <li>Text/newsletter content (blog system, markdown editor)</li>
32 32 <li>Creator subscriptions and one-time purchases</li>
33 - <li>100% revenue passthrough via Stripe</li>
33 + <li>0% platform fee, payments via Stripe</li>
34 34 <li>Full data export</li>
35 35 <li>Basic analytics</li>
36 36 <li>Hierarchical tagging and search</li>
@@ -162,7 +162,7 @@
162 162 <tr>
163 163 <td>Direct (our platform)</td>
164 164 <td>Live</td>
165 - <td>Full control, 100% revenue</td>
165 + <td>Full control, 0% platform fee</td>
166 166 </tr>
167 167 <tr>
168 168 <td>DSP (Spotify, Apple, etc.)</td>
@@ -56,7 +56,7 @@
56 56 <p>You retain all rights to content you upload. By uploading, you grant us a license to host, display, and distribute your content as necessary to operate the service.</p>
57 57
58 58 <h3>Revenue</h3>
59 - <p>You receive 100% of fan payments. Payment processing fees (Stripe) are deducted before funds reach you. We make money from your subscription, not your sales.</p>
59 + <p>We charge 0% platform fee on fan payments. Payment processing fees (Stripe, ~3%) are deducted before funds reach you. We make money from your subscription, not your sales.</p>
60 60
61 61 <h3>Prohibited Content</h3>
62 62 <p>Don't upload:</p>
@@ -32,7 +32,7 @@ You're responsible for your account credentials. Enable two-factor authenticatio
32 32 You retain all rights to content you upload. By uploading, you grant us a license to host, display, and distribute your content as necessary to operate the service.
33 33
34 34 ### Revenue
35 - You receive 100% of fan payments. Payment processing fees (Stripe) are deducted before funds reach you. We make money from your subscription, not your sales.
35 + We charge 0% platform fee on fan payments. Payment processing fees (Stripe, ~3%) are deducted before funds reach you. We make money from your subscription, not your sales.
36 36
37 37 ### Prohibited Content
38 38 Don't upload:
@@ -5,9 +5,9 @@ Quick answers to common questions.
5 5 ## General
6 6
7 7 ### What is Makenot.work?
8 - A platform for creators to sell content directly to fans. You keep 100% of revenue. We charge a flat subscription.
8 + A platform for creators to sell content directly to fans. 0% platform fee — only Stripe's ~3% processing applies. We charge a flat subscription.
9 9
10 - ### How do you make money if creators keep 100%?
10 + ### How do you make money with 0% platform fee?
11 11 Creators pay a monthly subscription ($10-$40 based on content type). We don't take a cut of sales.
12 12
13 13 ### Is it really free for fans?
@@ -6,7 +6,7 @@ When you support creators on Makenot.work, your money actually reaches them.
6 6
7 7 1. You pay for content or subscription
8 8 2. Payment goes directly to creator's account
9 - 3. Creator receives 100% (minus payment processing)
9 + 3. Creator receives payment (0% platform fee, only ~3% processing)
10 10 4. We make money from creator subscriptions, not your purchases
11 11
12 12 ## Ways to Support
@@ -20,7 +20,7 @@ For writers, bloggers, journalists, newsletter creators, and anyone whose primar
20 20 - One-time purchases for individual pieces
21 21 - Pay-what-you-want pricing option
22 22 - Subscription tiers you define
23 - - **100% of fan payments** (minus ~3% payment processing)
23 + - **0% platform fee** on fan payments (only ~3% payment processing)
24 24
25 25 ### Audience Tools
26 26 - Subscriber management
@@ -35,7 +35,7 @@ For video creators, filmmakers, educators, and anyone producing large content fo
35 35 - One-time purchases per video
36 36 - Pay-what-you-want pricing
37 37 - Subscription tiers you define
38 - - **100% of fan payments** (minus ~3% payment processing)
38 + - **0% platform fee** on fan payments (only ~3% payment processing)
39 39
40 40 ### Your Page
41 41 - Custom profile at `/u/yourname`
@@ -46,7 +46,7 @@ For musicians, podcasters, indie developers, plugin makers, and anyone distribut
46 46 - Per-track or per-album purchases
47 47 - Pay-what-you-want pricing
48 48 - Subscription tiers you define
49 - - **100% of fan payments** (minus ~3% payment processing)
49 + - **0% platform fee** on fan payments (only ~3% payment processing)
50 50
51 51 ### Your Page
52 52 - Custom profile at `/u/yourname`
@@ -42,7 +42,7 @@ For live streamers, broadcasters, and creators who need real-time video delivery
42 42 - Tips/donations during streams
43 43 - Pay-per-view live events
44 44 - Subscription tiers you define
45 - - **100% of fan payments** (minus ~3% payment processing)
45 + - **0% platform fee** on fan payments (only ~3% payment processing)
46 46
47 47 ### Your Page
48 48 - Custom profile at `/u/yourname`
@@ -50,8 +50,8 @@ See [Economics](../business/economics.md) for the breakdown.
50 50 - No external managed service dependency
51 51
52 52 ### Stripe
53 - - Standard Connect (creators own their accounts)
54 - - Env: STRIPE_SECRET_KEY, STRIPE_CLIENT_ID, STRIPE_WEBHOOK_SECRET
53 + - Express Connect (creators onboard via Account Links)
54 + - Env: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
55 55 - Requirements: verified business, bank account for platform fees
56 56 - Webhook events: checkout.session.completed, account.updated
57 57 - Exit: creators keep their Stripe accounts, migrate to backup processor
@@ -12,9 +12,8 @@ JWT_SECRET=your-super-secret-jwt-key-change-in-production
12 12 SESSION_SECRET=your-session-secret-change-in-production
13 13
14 14 # Optional: Stripe Connect (payments)
15 - # Get these from https://dashboard.stripe.com/settings/connect
15 + # Get these from https://dashboard.stripe.com/apikeys
16 16 # STRIPE_SECRET_KEY=sk_test_...
17 - # STRIPE_CLIENT_ID=ca_...
18 17 # STRIPE_WEBHOOK_SECRET=whsec_...
19 18 # HOST_URL=http://localhost:3000
20 19
@@ -78,12 +78,56 @@ dependencies = [
78 78 ]
79 79
80 80 [[package]]
81 + name = "anstream"
82 + version = "0.6.21"
83 + source = "registry+https://github.com/rust-lang/crates.io-index"
84 + checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
85 + dependencies = [
86 + "anstyle",
87 + "anstyle-parse",
88 + "anstyle-query",
89 + "anstyle-wincon",
90 + "colorchoice",
91 + "is_terminal_polyfill",
92 + "utf8parse",
93 + ]
94 +
95 + [[package]]
81 96 name = "anstyle"
82 97 version = "1.0.13"
83 98 source = "registry+https://github.com/rust-lang/crates.io-index"
84 99 checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
85 100
86 101 [[package]]
102 + name = "anstyle-parse"
103 + version = "0.2.7"
104 + source = "registry+https://github.com/rust-lang/crates.io-index"
105 + checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
106 + dependencies = [
107 + "utf8parse",
108 + ]
109 +
110 + [[package]]
111 + name = "anstyle-query"
112 + version = "1.1.5"
113 + source = "registry+https://github.com/rust-lang/crates.io-index"
114 + checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
115 + dependencies = [
116 + "windows-sys 0.61.2",
117 + ]
118 +
119 + [[package]]
120 + name = "anstyle-wincon"
121 + version = "3.0.11"
122 + source = "registry+https://github.com/rust-lang/crates.io-index"
123 + checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
124 + dependencies = [
125 + "anstyle",
126 + "once_cell_polyfill",
127 + "windows-sys 0.61.2",
128 + ]
129 +
130 + [[package]]
87 131 name = "anyhow"
88 132 version = "1.0.101"
89 133 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1190,6 +1234,46 @@ dependencies = [
1190 1234 ]
1191 1235
1192 1236 [[package]]
1237 + name = "clap"
1238 + version = "4.5.60"
1239 + source = "registry+https://github.com/rust-lang/crates.io-index"
1240 + checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
1241 + dependencies = [
1242 + "clap_builder",
1243 + "clap_derive",
1244 + ]
1245 +
1246 + [[package]]
1247 + name = "clap_builder"
1248 + version = "4.5.60"
1249 + source = "registry+https://github.com/rust-lang/crates.io-index"
1250 + checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
1251 + dependencies = [
1252 + "anstream",
1253 + "anstyle",
1254 + "clap_lex",
1255 + "strsim",
1256 + ]
1257 +
1258 + [[package]]
1259 + name = "clap_derive"
1260 + version = "4.5.55"
1261 + source = "registry+https://github.com/rust-lang/crates.io-index"
1262 + checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
1263 + dependencies = [
1264 + "heck",
1265 + "proc-macro2",
1266 + "quote",
1267 + "syn 2.0.114",
1268 + ]
1269 +
1270 + [[package]]
1271 + name = "clap_lex"
1272 + version = "1.0.0"
1273 + source = "registry+https://github.com/rust-lang/crates.io-index"
1274 + checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
1275 +
1276 + [[package]]
1193 1277 name = "cmake"
1194 1278 version = "0.1.57"
1195 1279 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1208,6 +1292,12 @@ dependencies = [
1208 1292 ]
1209 1293
1210 1294 [[package]]
1295 + name = "colorchoice"
1296 + version = "1.0.4"
1297 + source = "registry+https://github.com/rust-lang/crates.io-index"
1298 + checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
1299 +
1300 + [[package]]
1211 1301 name = "concurrent-queue"
1212 1302 version = "2.5.0"
1213 1303 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3063,6 +3153,12 @@ dependencies = [
3063 3153 ]
3064 3154
3065 3155 [[package]]
3156 + name = "is_terminal_polyfill"
3157 + version = "1.70.2"
3158 + source = "registry+https://github.com/rust-lang/crates.io-index"
3159 + checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
3160 +
3161 + [[package]]
3066 3162 name = "itertools"
3067 3163 version = "0.14.0"
3068 3164 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3357,7 +3453,7 @@ dependencies = [
3357 3453
3358 3454 [[package]]
3359 3455 name = "makenotwork"
3360 - version = "0.1.0"
3456 + version = "0.1.4"
3361 3457 dependencies = [
3362 3458 "ammonia",
3363 3459 "anyhow",
@@ -3371,6 +3467,7 @@ dependencies = [
3371 3467 "axum-extra",
3372 3468 "base64 0.22.1",
3373 3469 "chrono",
3470 + "clap",
3374 3471 "dotenvy",
3375 3472 "git2",
3376 3473 "goblin",
@@ -3901,6 +3998,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
3901 3998 checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
3902 3999
3903 4000 [[package]]
4001 + name = "once_cell_polyfill"
4002 + version = "1.70.2"
4003 + source = "registry+https://github.com/rust-lang/crates.io-index"
4004 + checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
4005 +
4006 + [[package]]
3904 4007 name = "openssl"
3905 4008 version = "0.10.75"
3906 4009 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6526,6 +6629,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
6526 6629 checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
6527 6630
6528 6631 [[package]]
6632 + name = "utf8parse"
6633 + version = "0.2.2"
6634 + source = "registry+https://github.com/rust-lang/crates.io-index"
6635 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
6636 +
6637 + [[package]]
6529 6638 name = "uuid"
6530 6639 version = "0.8.2"
6531 6640 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makenotwork"
3 - version = "0.1.0"
3 + version = "0.1.4"
4 4 edition = "2024"
5 5 license-file = "../../LICENSE"
6 6
@@ -65,6 +65,9 @@ goblin = "0.10"
65 65 zip = "8.2"
66 66 yara-x = "1.13"
67 67
68 + # CLI
69 + clap = { version = "4", features = ["derive"] }
70 +
68 71 # Error handling
69 72 thiserror = "2.0.18"
70 73 anyhow = "1.0.101"
@@ -93,6 +96,10 @@ url = "2.5.8"
93 96 # Error tracking
94 97 sentry = { version = "0.38", default-features = false, features = ["backtrace", "contexts", "panic", "tracing", "reqwest", "rustls"] }
95 98
99 + [[bin]]
100 + name = "mnw-admin"
101 + path = "src/bin/mnw-admin.rs"
102 +
96 103 [dev-dependencies]
97 104 tower = { version = "0.5.3", features = ["util"] }
98 105 http-body-util = "0.1"
@@ -0,0 +1,15 @@
1 + -- SyncKit blob storage: tracks uploaded file blobs per app/user.
2 + -- Blobs live in a separate S3 bucket, keyed by content hash for dedup.
3 +
4 + CREATE TABLE sync_blobs (
5 + id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
6 + app_id UUID NOT NULL REFERENCES sync_apps(id) ON DELETE CASCADE,
7 + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
8 + hash TEXT NOT NULL,
9 + s3_key TEXT NOT NULL,
10 + size_bytes BIGINT NOT NULL,
11 + uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
12 + UNIQUE(app_id, user_id, hash)
13 + );
14 +
15 + CREATE INDEX idx_sync_blobs_lookup ON sync_blobs(app_id, user_id, hash);