cd /news/ai-startups/built-a-rival-to-the-largest-fanfict… · home topics ai-startups article
[ARTICLE · art-35358] src=obaid.wtf ↗ pub= topic=ai-startups verified=true sentiment=· neutral

Built a rival to the largest fanfiction platforms – alone, at 17

A 17-year-old developer built a rival to the world's largest fanfiction platforms alone, using a $5/month server, drawing on a childhood of restricted reading and technical tinkering. The project emerged from personal experiences with censorship and a drive to create unrestricted access to stories.

read28 min views1 publishedJun 21, 2026

co-authors: Obaid, Opus4.8

this post is an experiment in parallelizing the power of LLM research with telling my own story, something I've found surprisingly difficult to do. if this works (in terms of telling my story the way i want to), expect more

the narrative outside blocks is all mine, and within all Opus 4.8

Prologue #

Since I was 7 years old, I’ve been an avid reader. When I look at how old kids are at 7 now it is hard to believe myself, but thankfully I do have the receipts which means I don’t end up gaslighting myself — stale digital records of torrented books and pixelated videos through Nokia handhelds and me reading as people shout, tables fall, and my little world (the home I knew) descends into chaos all around.

Of course, I was a technically savvy kid. At around the same age (I don’t remember when), there was an incident: somehow, I ended up changing the WiFi password of our network without knowing anything or ever remembering using the 192.168… local address. To this day, I don’t know how that was possible, but I have a backup in trusted, shared memory from my brother who still remembers pressing me on how I did that — the answer I had for him then is the same I would have now: I was just playing with the settings and I don’t know.

Later when my brother (CS major, top of his batch, and one of the first iOS developers at Venture Dive/Careem) got too busy, this meant the duties of managing our home’s technical admin naturally fell to me.

Git can't confirm the WiFi story, but it fits the rest of what the commits show. Whoever wrote these 298 commits learned by breaking things instead of reading the manual first. Deleting wp-includes

one folder at a time just to see what would fall over is the same kind of thing a seven-year-old does messing with the router settings.

I was also devious: I remember using carefully placed mirrors to find people’s device passwords and secretly used them at night when no one could find out — having bounded creativity and strict parents will do that to you.

The same restrictions extended to books. The way I used to devour books grew from something initially supported into apparently serious concern. My book-reading time became limited, and so did the restrictions on their content. My mom didn’t understand it, and so she put a halt to it. To this day one of the principles I’ve derived for my life has an origin in learning of how harmful this is.

I remember when I borrowed a book from the son of a family friend, Alex Rider: Eagle Strike, my mom had my brother read it cover-to-cover and with a black marker erase all text that had anything to do with girl-to-boy interactions or “kissing”. I can’t tell you what it did to my psyche over the next 10 years, but I can tell you that extensive control extended far further.

I cried when I found out, not because of the censorship, that I was used to, but because I didn’t know I could explain the markings when giving it back. And my mother knew who I’d borrowed it from, so it was actually even more insane. I’m almost cracking up thinking about the absolute insanity of it now. Though in hindsight, that was probably for the better: at least someone from the outside had a window into this insanity.

This is sounding more like a confessional therapy session than it does a story about a fanfiction site, but perhaps that is the necessary prelude I’ve been wanting to put out all along.

I learned to torrent because of my obsession with books too. My mom used to take me to a used book stall in Hyderi, where the books were stacked taller than our height, no categorization, and prices were between Rs 100–400. Our monthly trip had a limit of 4 books per sibling, no more, and that, obviously, was not nearly enough for me.

I learned to torrent. I knew what it was because my siblings used to download the matriarch-approved flicks to put on show for the family, but the recipes for those were always gatekept. As anything as an early GenZ, I used the internet to find that recipe.

The books I downloaded were nearly always continuations of series I’d started but never found the full cycles for, and soon I remembered not to beg my mom to go to Liberty Books or ask for books I couldn’t find at the stalls. My reading had become all-digital.

I remember on our first-ever out-of-Karachi trip, to Islamabad, we stumbled into Saeed Book Bank and I found on the shelf the then newly-released finale of the Artemis Fowl series: The Last Guardian. I was hooked, but I knew to skim through for as long as we were there, and then quietly get up only to launch uTorrent the moment I got back home to Karachi.

Soon, that desire for continuation turned into something else: the discovery of fanfiction. A way to stay in denial about the stories that I’d learned had already ended.

My sister had a role in that introduction to this world, and learning what I did later about the depths of insanity it went to, I often wondered about her adult due-dilligence in that.

My life up till that point represented the new. I was determined to turn everyone using anything backwards up to that point — whether that was paper records not yet adapted to Google Sheets, or literally anything else — into the “modern” way. And that desire extended to my newly discovered fanfiction community with a new website, the “modern” way of reading fanfiction.

fanfiction.online.

Chapter One · The Build #

I launched this under an anonymous name, very easy to do as I’d found in my Reddit stalking, primarily out of fear of retribution from my family or other circles I was in accidentally discovering me.

And I began making a more modern website. The thing is, I didn’t know how. I’d used WordPress to create websites before and my early foray into it at 8/9, learned to make HTML and JavaScript websites, but the only full live tool I knew how to use was WordPress. I’d used WordPress to create a few sites then, and chose to use it again. And I did.

I knew WordPress.org existed with more customization. And I knew how to use one-click deployments. So I used my dad’s newly (badly sustaining) business website that used a cPanel hosting and that I managed, and set up an alternate site on it.

And it was live. But the preset theme I used didn’t make sense. And the admin panel, despite all the hooks and customizations to remove panels, was still clunky. And I didn’t want the WordPress logo to show anywhere.

So one-by-one, step-by-step, in one of the greatest learning curves I think I have surpassed in my life, I learned to build all of that on PHP and from scratch, learning what code even was along the way. The process was simple: I saw a button I didn’t like, and googled “hook to remove xyz button”, kept researching until I found the right guide, article, reddit thread, or stack overflow post. Tried a few, then dug deep into the HOW when I found one that worked.

Slowly, this turned into me replacing the entire admin panel with a custom one, using the same APIs, and then even not, stripping away all aspects of the WordPress core until I was able to delete the entire wp_includes directory and only the MySQL database schema remained.

By 2020 it wasn't WordPress — only its MySQL tables remained. A custom PHP Router

in one index.php

caught every request; templates, auth, mail, and analytics were all rewritten, one Stack Overflow answer at a time, on a $5/mo shared cPanel plan. The real code is the wp_ffonline

repo's Custom-Routing

branch — 298 commits to master

's 6. The tell: an April 2020 message, "removed wp-includes"

.

Earliest proof of life

A Wordfence login alert — user obaid

, IP 45.116.232.52

— is the oldest trace of fanfiction.online

. WordPress already live, Wordfence installed. Seven months before git opened.

Git enters a site that already exists

300+ files in one commit: custom theme book-writer

, three plugins. No version control before this — the first commit is everything that already existed, dropped in at once.

"Added SMTP"

PHPMailer wired in. The site can send verification codes. First of three mail architectures.

Version 15.x.x: 98 commits in 60 days

15.5 through 15.10, sometimes three releases in a day. The numbers stopped meaning anything; the shipping didn't slow.

"Updated Mail templates"

The last thing git recorded. Mail broke the site when it finally mattered — fixing it was the final commit.

Those "Version X.Y.Z" tags aren't versions — they're deploys: the whole tree zipped and pushed, git as a save button. They contradict each other (v15.10.8

predates v15.10.0

; 15.8.2/15.8.3 share one commit), and the largest, 15.7.4

, touched 276 files at once. So 298 commits is a lower bound — much was built between snapshots git never saw:

Commit Files Changes What it actually was
v15.7.4 276 +2,064−56,428 Major rewrite + dead code purge
v15.10.7 38 +1,634−483 Notifications + poll system overhaul
v15.10.0 (×2) 71–76 +1,596–1,806−347–805 Two separate deploys with the same name
v15.9.0 (×2) 28–61 +327–774−209–321 Same version number, different branch snapshots
v15.8.0 80 +856−45,726 WordPress cleanup: 45k lines deleted
v15.6.0 93 +837−7,274 Full frontend restructure
v15.6.1 + v15.7.0 50 +1,285−325 Two versions, one commit

The bundler

In 2020, webpack and rollup existed but targeted single-page apps; a server-rendered PHP site had no equivalent. So he wrote his own: a PHP manifest declares each page's bundle, and a Python compiler resolves it, skips unchanged sources, minifies with terser and cssnano, and writes content-hashed name-<sha1>.js

files — cache-busted in production, raw in dev for honest stack traces. Incremental builds, content-hash cache-busting, shared chunks: the ideas webpack shipped, arrived at independently.

The same build.py

does double duty: besides compiling assets, it's also the deploy script — SCP the build up, SSH in to unzip it live — this comes up later, in the crash.

A page declares a bundle — its JS/CSS plus shared mix

groups.

"global":{"js":["intro"],"mix":["idb"]}

↳ an entry, not a file — nothing compiled yet Pulled into a PHP page by name.

<?= bundle('global') ?>

↳ dev emits the raw sources; prod, one cache-busted tag build.py

resolves & minifies only the changed bundles.

name-<sha1>.js

↳ skips unchanged sources, writes the hash back into the manifest

Chapter Two · The Platform #

The site wasn’t one feature. It was sixteen interlocking systems — each its own PHP class, its own database tables, its own notification hook — built by someone who’d never heard the term “microservice” but arrived at the shape anyway.

Not a feature list — a directory listing. Each .php

file is a standalone system with its own DB schema, its own API endpoints, and its own hook into a shared notification dispatch. Grouped by what they do:

Search.php

Inverted index, set algebra in memory, zero SQL per filterFeed.php

Personalized by fandom + rating + language + currently readingCollections.php

Curated story lists, followers notified on updateBookmarks.php

Exact paragraph position, server-side, per userDrafts.php

Folder + branch types, private until publishedUpdates.php

Author blog posts on profiles, pinnableBeta.php

Invite-only reader access, time-limited sessionsImport.php

Link FFN account → auto-syncs chapters on upstream updateReviews.php

Threaded: quote + reply, parent-ID treeVote.php

Per-chapter, fires author notificationFollow.php

Authors or collections, per-follow notification prefChats.php

User-to-user DMs with blockingPoll.php

3-day expiry, results pushed via notificationQuestions.php

Anonymous submit, author approves → publicNotifications.php

10 priority-ranked types, single 15s poll heartbeatStats.php

VFS identity, impressions vs views, referrer trackingThemes.php

Normal / sepia / dark, saved per accountThe notification system was the spine. Ten event types — story_update

, chapter_review

, review_reply

, follow_user

, follow_collection

, chapter_vote

, user_update

, beta_invite

, stories_imported

, account_verified

— all delivered through a single 15-second poll heartbeat (described in the next chapter).

Authors and readers I reached out to, to join the site, often mentioned they’d love an app and would prefer it over a website. And after hours of researching the tradeoffs of WebViews, taking on the complexities and learning of React Native, and much much more — this was the first decision I made to prioritize, of choosing not to build something.

I instead maximized every single, however-new-feature of PWAs and turned the entire website in something that background-auto-downloaded into an offline app the first time you visit, and where you could save any story to read offline with just button, and read again, Wi-Fi off, directly from your browser.

Even today in 2026, there is barely a website I know of that manages to do this: even with the ease of Claude Code simplifying all the weird complexities of service workers into a couple back-and-forth chats.

The reader also worked offline, modeled on a library. "Borrow" a story: service worker caches the story page and every chapter URL, IndexedDB tracks what you hold. "Return" it to free space. Hourly, the worker sends held IDs + last sync timestamp; the server diffs and responds with three lists — current

(keep), update

(re-cache changed chapters), delete

(evict unpublished). Only deltas transfer. Reads done offline queue locally and reconcile on reconnect, stamped with the same landing_id

that ties into the analytics system in the next chapter.

Chapter Three · The Measurement #

Perhaps my crown jewel and most loved-feature, inspired by the conspiracy theories of Zuckerberg, his taped laptop camera, and how he tracked every single movement of every person and what they were thinking. I was an easily-inspired child, and I wanted to build something of my own like it.

No Google Analytics — the platform watched itself. Every page view saved a row to stats_landings

, and it counted two different things: an impression (a story showed up in your results) versus a view (you actually clicked it). The difference is what people saw but skipped.

To make that data mean anything, it needed to know who each row belonged to — even before someone logged in. So every visitor got a VFS (Visitor Fingerprint Signature): a single ID that sticks to you across visits, and stays the same if you browse anonymously and sign up later. To find it, the server tried four things in order, stopping at the first hit:

Cookie

Already have a

vfs

cookie? Use it, and refresh it for another 15 years.User ID

Logged in? Look up their first-ever visit and reuse that VFS.

IP address

Seen this IP before? Reuse the VFS it had last time.

New VFS

Truly new visitor — mint a fresh random ID with

bin2hex(random_bytes(39))

.Impressions fired by scroll, not render — a card counted only once its midpoint crossed the viewport, flushed every 15s to one endpoint: poll

. That call became the real-time backbone, accreting jobs over six months:

Impressions ship

im()

lands in global.js

: counts a story card once it's actually on screen, sent to poll

every 15s.

Custom tags take over

The scanner switches to the invented <book>

tag, tying analytics to the homegrown tag system.

Poll delivers notifications too

The same 15s call now returns your new notifications on the way back — one round-trip for both.

PollFilters: the loop becomes a bus

PollFilters

added: any page can attach extra data to the outgoing poll before it sends.

Reading position joins the wire

chapter-tracking.js

uses it to also send which paragraph you're on. Now one request carries impressions, notifications, and reading position.

One request ended up doing everything. An AES-256 placeholder

token, minted at render, stamped every action — vote, review, search — back to the VFS and referrer that opened the session:

placeholder

AES-256 token — landing id + page typenonce

per-page session guardim_books[]

cards scrolled past the viewport midpointposition

paragraph now at the viewport topnotifications[]

inbox diff since the last pollunread

badge countplaceholder

landing_id

nonce

→ session · mismatch rejects with 998

, logged as spamstats_actions

· save paragraph → track_reading

Both rode in the DOM as invented HTML tags, read via .getAttribute('value')

— web components, years before they were actually in the mainstream. On top, two cached admin reports: session time by referrer, and link-ins counted per story rather than sitewide — so the Tumblr spike showed up as one domain flooding a single row.

Chapter Four · The Outreach #

So I engineered a scraper that bypassed Cloudflare through a rotating outwards proxy, logged into fanfiction.net with 5 rotating accounts, and sent personal DMs to a total of 8,500+ top authors, inviting them to the website. I don’t think I knew what cold outreach or email lists even was at the time, but I knew I wanted to speak to authors, and I’d figured out it was a numbers game.

All on a $5/mo cPanel hosting, shared with one more, though mostly dead website.

One engine both stocked the library and ran the outreach. Scraping FFN — the largest fanfiction archive online, behind Cloudflare and hostile to scrapers — took over a year of building, getting blocked, and rebuilding across two repos: ffarchive_py

, then ffn_scraper

.

ffarchive_py is born

Scrapy + MongoDB. FFN pagination working in days — and blocked just as fast.

From crawler to growth engine

Gains a story importer, live update loop, and auto-sender — it stops just reading FFN and starts messaging it.

Clean-slate rewrite

Proxy tricks stopped working. Rebuilt as ffn_scraper, using Flaresolverr to actually beat Cloudflare.

"All in SQLite DB"

MongoDB dropped for UserData.db

— 8,378 authors indexed, with verification and notifications wired in.

Added FFN Sender

Scraper and outreach merge — the crawler feeds the PM queue directly.

Both engines, last touch

Both scrapers get their final commits 60 seconds apart — "Stuff"

and "Something"

, set down at 5am.

The verification handshake

FFN had no API or login to plug into, so there was only one way to prove an account was yours: show you could read its messages. The site gave you a code and a pre-filled message link, and a bot (ffn_verification.py

) watched the inbox for that code and tied it to your FFN account. Once it matched, every story already scraped under that account was handed to you. Scraped first, returned when the real author showed up — which is what people objected to (covered later in this piece).

You paste your FFN user ID and hit submit. The site generates a one-time code, good for an hour.

verify_user

bin2hex(random_bytes(4))

You click the link — it opens FFN's message box, already addressed, code already copied. Just paste and send.

pm2/post.php?uid=13818620

· _.copyText

A bot checks that inbox on a loop and reads your message. It pulls the sender's author ID and the code.

/pm2/inbox.php

via Flaresolverr (own proxy, solves Cloudflare headless)Code matches — you're verified. Fires an

account_verified

notification and hands every story already scraped under that author ID over to you.FFN sits behind Cloudflare, so every request to that inbox had to clear a bot check first. Each one routed through Flaresolverr, a headless browser that runs Cloudflare's challenge and returns the cleared cookies — held per session and reused, not re-solved each time. The PM reader and the bulk crawler ran on separate proxies and identities, so the account reading messages never shared an IP with the one pulling stories. When FFN logged a session out, the bot caught the redirect, signed back in, and kept going. No official API existed; the whole pipeline ran on reverse-engineered cookies and held together for over a year.

The targeting filter: only authors with 350+ favorites or follows. 8,378 profiled, 4,964 reached, one message every 35 seconds across 5 rotating accounts, with each delivery confirmed by parsing the response page. Every one of them got this:

We've created a site at Fanfiction Online for reading and writing fanfiction, since we feel FFN (and other sites) are really outdated and lack a lot of stuff.

Would love if you could post your stories there. You can reach new readers, and since the site has better reading, your current readers will read your fanfics more comfortably as well.

You might find it difficult to post on another site, so the site will do all the heavy lifting for you :) All you have to do is link your FFN account and select the stories you want to import. It's as simple as that. The stories will be updated automatically whenever you update the fic on FFN.

Thanks! Let me know if you have any questions.

The auto-sender wasn't random: it ranked UserData.db

authors by engagement and worked from the top down. The five highest held 1.3M favorites between them:

Author Favorites Fandom
sakurademonalchemist 456,019 Harry Potter · KHR
Lomonaaeren 284,724 Harry Potter
DebsTheSlytherinSnapefan 212,470 Harry Potter
Tsume Yuki 207,734 Naruto
NeonZangetsu 194,866 Naruto · Fate/stay night

After each send, it parsed FFN's response page to confirm the message landed.

The response wasn’t great. I learnt that people wouldn’t use an empty site, less than 10 out of 8,500+ of them. But it did get some results — multiple authors created threads on Reddit, Tumblr, and other forums about the horror of being approached like this and how I was a money-sucking leech (I’d never monetized it, but they assumed somehow) bonding in their trauma. Yes, the response was that negative, almost bordering on harassment.

Reconstructed from u/eqwe32 — the anonymous account he posted the project under at the time — and all 51 of its threads across the fanfiction subreddits. One correction the record makes: the objection was rarely about money. It was about consent. Across 15 of those 51 threads, readers and writers make the same charge, that hosting their stories without asking was not his to do.

The public poll he ran on whether to remove those fics, instead of just removing them, sharpened it rather than settling it (u/Cautious-Pirate, 9 pts: he'd "got blasted for this left, right and centre").

And the hostility tracked what he posted, not who he was: asking a community what to build drew help and line-by-line feedback; announcing he'd built the best site drew the harshest replies. The cold-DM campaign this section is about came late, and was never the main charge against him.

Every public post from that account, in order:

"Hi everyone!" · 4↑ · 8 comments

First public trace — and the consent objection is already in the replies.

"Ideal features you'd like to see..." · 0↑ · 15 comments

Scraping becomes the headline. Top reply, u/gros-grognon (11↑): "downright shitty... I can't trust your site."

"Suggestions needed on how to improve..." · 13↑ · 32 comments

Pure UX feedback — layout, mobile, title-vs-author hierarchy — answered point by point.

"Something you've (probably) all been waiting for." · 62↑ · 20 comments

Warmest reception; readers say they'll switch. One won't — same consent grounds (u/Atojiso, 14↑).

"What would be your dream fanfiction writing app?" · 0↑ / 39↑

Same feature-mining question put to both subs in one week.

Own subreddit created · 1↑ · lounge only

Spins up a project subreddit. Never takes.

"I created a fanfiction site..." · 219↑ · 167 comments

Biggest hit — pitched to Naruto writers before the site had a Naruto option (u/Cranesbill, 65↑).

"...any suggestions/questions?" · 38↑ · 37 comments

The pitch at its plainest: no existing site improves on what readers and writers actually ask for.

"The best app/site... you won't be disappointed." · 107↑ / 13↑

Hard-sell framing flips the room. u/callmesalticidae (56↑); u/Bomaruto (34↑): no right to host uploads without a terms of service.

"Fanfiction.online will sunset on Sep 15th 2023" · 0↑ · 11 comments

End notice, three years on. The Dec 2019 complaint is still the first in the replies.

I was struggling — months of development and all other corners of fanfiction communities, the people I was building it for, seemed to hate me. Only one corner of the internet, a Harry Potter-centered fanfiction Discord, seemed to welcome me and provided support and, might I say, older developers who offered me guidance.

Until my big lucky break.

Chapter Five · The Crash #

A viral post on Tumblr ranted against Ao3 for various reasons and referenced my website as where they were moving. It was a miracle — overnight from 50 I hit 10,000+ signed-in users and could have been a lot more, except the cPanel hosting finally gave up, the internal email service I had used to deliver verification codes refused to deliver, and I was left stranded.

In all times of difficulties since then in my life, I’ve always had an inkling of an idea of what I would do. In this case, I had none.

Shared cPanel hosting, $5/mo, shared with one other barely-alive site. The host's internal mail service buckled under load. New users couldn't verify accounts. The hype was live; the site was effectively dark. No fallback, no queue, no redundancy — there had been no reason to build one.

At the same time, anticipating some need I had started making some money and had a whole $35 stuck in Payoneer. I knew within those limits I had to figure out a solution. So I started researching and learning what servers were — I spent night and day, and in the most literal sense possible. I didn’t sleep for nearly 3 nights and days worried I would miss out on my lucky break of users — and I wasn’t wrong, I did.

And I wasn’t able to figure the migration and setup of my newly discovered $5 droplet on DO in time — the site was unavailable, and the hype died.

The migration did eventually get there. Two separate webapps, fanfiction_online

and beta

, both on the same $5 droplet, deployed from a laptop via build.py

.

The deploy script is about as basic as it gets. No CI, no Docker, no staging pipeline. Just Python calling subprocess

twice to SCP two zips up, then SSHing in to unzip them into the live directory. It looks naive, and in some ways it is, but it's also enough when you're 17, have $35, and are racing a hype window that's already closing. It worked. Both webapps, main and beta, on the same droplet, deployed from a laptop:

host = "root@167.99.11.178"
upload_to = "/home/runcloud/webapps/"

subprocess.call("scp content.zip " + host + ":" + upload_to, shell=True)
subprocess.call("scp static.zip  " + host + ":" + upload_to, shell=True)

cmds = [
    "rm -r -f " + upload_to + main_app + "/content",
    "unzip content.zip -d " + upload_to + main_app + "/",
]
subprocess.call("ssh " + host + ' "' + " && ".join(cmds) + '"', shell=True)

The crash timeline, reconstructed from the commit dates and what the code tells us about the mail architecture:

50 → 10,000+ signed-in users overnight

A viral post referencing the site as the alternative. No warning, no ramp-up.

cPanel's mail collapses under signup load

Verification codes stop. Every new account is stuck at the confirmation step. The hype is live; the funnel is dead.

Learning what a VPS actually is

DigitalOcean found. root@167.99.11.178 provisioned. $35 in Payoneer, the entire budget. RunCloud installed for server management.

Migration finishes; hype is already gone

The window was days wide. The setup took longer. 10,000 users became a number in a story about what almost was.

Both webapps live on the $5 droplet

fanfiction_online

and beta

. Deployed via build.py

. The infrastructure that should have existed in March.

Chapter Six · The Engine #

After the crash, I did some rudimentary benchmarking — eyeballing the DO instance’s memory graph, inserting timers into PHP template scripts. One of the two biggest reasons for the crash: WordPress’ query engine took on average 20s for a single filtered query, sometimes up to 2 minutes.

Rather than fidget against WordPress for a 2-3x speedup, I hand-rolled it all. PHP 8 had just been released with promises for efficient low-level array functions, so I built my own query engine and brought 90% of complex queries down to < 3s.

The search was the worst offender — nine filter dimensions (fandom, genre, rating, status, word count, character, pairing, language, sort), each supporting include and exclude. Under WP_Query

, every combination compiled to JOINs against wp_term_relationships

. The replacement: an inverted index precomputed at write time — every tag value mapped to the set of story IDs carrying it — filtered entirely in memory as set algebra.

No joins. No WHERE tag IN (...)

against 50k rows. Result set computed in memory; only current-page IDs fetched from DB. Wrapped in a hand-built PHP 8 type system (Integer

, LazyInteger

, Range

, Str

, OneOf

, TypedArray

) — all throwing on bad input. Two commits, then the repos went quiet.

Toggle filters — click to include (green), again to exclude (red), third to clear:

Chapter Seven · The Mail Server #

I was finally able to get it together and set up my own mail server, but that too, was eventually rate-limited.

Three generations of email, each one failing in its own way, and each failure only obvious once it was already costing users.

Buckled at the spike

Fine for low volume. Couldn't survive 10,000 signups overnight. Verification codes stopped. New users were stuck at confirmation.

Cold IP, throttled out

Worked initially. Then receiving servers started rejecting it, which is the usual fate of any unknown IP sending volume. No reputation, no delivery.

Finally held

Seven fully-designed transactional templates. The solution that should have existed before the spike, built three months after it.

The last two commits in the entire project — "Sending Mail"

and "Updated Mail templates"

, May 20, 2021. A fitting last word from the code: the thing that crashed the site when it finally got its moment, fixed, at the very end.

SendGrid was the third attempt and the one that held: seven fully-designed dynamic templates, each with a real SendGrid template ID hardcoded:

Seven templates means seven distinct transactional scenarios were thought through, designed, and wired to API calls. That's more deliberate than email usually gets on a project this size, and it reads like someone who'd already watched it fail twice and wanted the third version to hold.

Epilogue #

The commit messages decay honestly as the months drag on — "Whatever,"

"Stuff,"

"Something,"

"No Idea what this is."

That's not failure. That's the fingerprint of someone carrying an entire product on their own back, well past the point most would have stopped.

The hype window closed before the migration was ready, and the outreach campaign produced backlash before it produced users. All of it ran on a $5 shared hosting plan the site was never meant to outlive, until the night it had to, when three sleepless days and the only $35 available went into fixing that.

The repos went quiet on May 20, 2021, with two commits about finally getting email right. The live site kept going and kept gaining features that version control never recorded. The story git can tell ends there.

Solo teenage projects usually follow one pattern: a good idea, built halfway, then dropped. This one ran longer than that. The commit history shows someone who kept going through the backlash, the crash, and the migration that came too late. The final commit is about email. Not a new feature or a rewrite, just a fix for the thing that had failed at the worst possible moment, landing three months after it mattered.

The commit messages do get worse. "Whatever"

, "Stuff"

, "Something"

, "No Idea what this is"

turn up in the middle months. That reads less like someone who stopped caring and more like someone too far into the work to bother naming it. What got built across that stretch: sixteen features, a custom PHP framework, a Cloudflare-bypassing scraper, a typed search engine, three generations of email infrastructure, and a cold-outreach campaign to 8,500 authors, all of it alone, at 17, on $5 a month.

The repos went quiet on May 20, 2021. The story they can tell ends there; the rest survives only because it finally got written down.

The site outlived its code by two years. The shutdown, announced July 23, 2023, came with a one-line reason: "Too expensive to maintain (and so my therapist stops bothering me about it)." He pitched a successor, a free Chrome and Edge reading extension for AO3 and fanfiction.net, and held his ground on the original idea: "I don't think what I did was stealing in any way, it's exactly the same... as the WaybackMachine."

Within a day he'd talked himself out of the extension too, "because of the hostile reaction on the other FanFiction sub." Roughly three and a half years of running it, and what stopped it in the end was cost and exhaustion, not the code.

The public traces the site left behind, if you want to read the reception unfiltered:

· Reddit — the launch and the backlash, in full: u/eqwe32

· X / Twitter — the project account: @FanfictionOnlin

· Tumblr — what other communities said: "fanfiction.online" search

── more in #ai-startups 4 stories · sorted by recency
── more on @obaid 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/built-a-rival-to-the…] indexed:0 read:28min 2026-06-21 ·