$ cat /blog/launching-without-permission.md

Launching Without Permission: How I Shipped a Real MVP Without Formal Sign-Off

How I shipped a real MVP without formal sign-off: validate fast, limit risk with reversible bets, and learn from real users.

Launching Without Permission: How I Shipped a Real MVP Without Formal Sign-Off

Category: startups

Everyone told me I needed a committee, a PRD, and three rounds of stakeholder reviews before shipping. I didn't have any of that. I had an idea, three weeks, and a shrinking runway. So I shipped an MVP without formal sign-off — safely, deliberately, and with real users paying within 48 hours.

This isn't a manifesto to bulldoze process. It's a practical guide on how to validate fast while keeping risk reversible and measurable. If you're a solo founder, small team, or team stuck in slow approval loops, these are patterns that actually work.

Why launch without permission?

Formal sign-off exists to reduce risk: product risk, legal risk, operational risk. But it also slows down learning. For early-stage products the biggest risk is building the wrong thing. The fastest way to de-risk that is to show something to real users and learn.

I've found that:

  • Ideas die in meetings, not in production.
  • Users reveal hidden assumptions much faster than spec reviews.
  • You can reduce the non-product risks (legal, security, ops) to an acceptable level with deliberate, reversible controls.

So the question becomes: how do you keep the non-product risks manageable while maximizing the speed of learning? The answer is "reversible bets" and tight instrumentation.

Reversible bets: the mental model

I treat every early change as a reversible bet. A reversible bet is a change you can undo quickly if it goes sideways and that contains risk to a bounded surface area.

Examples:

  • Feature flagged rollouts (toggle off instantly)
  • Soft launches (invite-only, limited traffic)
  • Read-only schema changes (add columns, don't transform data in-place)
  • Proxying traffic through a feature service (no permanent routing changes)

The main constraints for a reversible bet:

  • Fast rollback (minutes)
  • Small blast radius (limited users, limited features)
  • Observable (metrics and logs that show impact within minutes)

If a change can't be reversed quickly or its blast radius is large, it needs proper sign-off.

Patterns I use to launch fast (and safe)

Below are the concrete patterns and snippets I rely on.

1) Feature flags everywhere

I use feature flags for almost every new user-facing thing. For early work I avoid third-party flagging services — a simple in-app check is enough.

Node/Express example (simple Redis-backed flags):

// flag.js
const redis = require('redis').createClient();
async function isFeatureEnabled(userId, feature) {
  // key: feature:users -> set of enabled userIds
  return new Promise((res, rej) => {
    redis.sismember(`flag:${feature}:users`, userId, (err, reply) => {
      if (err) return rej(err);
      res(Boolean(reply));
    });
  });
}

// middleware
module.exports = (feature) => async (req, res, next) => {
  const userId = req.user && req.user.id;
  if (!userId) return res.status(401).send('auth required');
  if (await isFeatureEnabled(userId, feature)) return next();
  res.status(404).send('not found');
};

This lets me enable a feature for a handful of users instantly. Rollback is removing users from that set.

If you prefer hosted tooling: LaunchDarkly, GrowthBook, or Flagsmith are solid options. For early MVPs I keep it simple.

2) Soft launches and invite lists

I never open the floodgates day one. I start with a list of 50–200 users — friends, newsletter subscribers, or people who expressed interest. That keeps load, legal, and support manageable.

Command-line workflow to add a user to a flag:

# add user 42 to early-access feature
redis-cli SADD flag:early_access:users 42

If something breaks, I remove the set and the feature disappears.

3) Safe migrations — add before change

Never do destructive migrations during an MVP launch. Use this pattern:

  1. Add new column/table
  2. Backfill if needed in a separate job
  3. Switch code to read/write new fields
  4. Drop old columns after a stabilization period

SQL example:

-- step 1
ALTER TABLE customers ADD COLUMN liked_feature BOOLEAN DEFAULT NULL;

-- step 2 (backfill in background)
UPDATE customers SET liked_feature = false WHERE liked_feature IS NULL;

-- step 3: app reads/writes liked_feature
-- step 4: DROP COLUMN after 30 days

This approach makes schema changes reversible and predictable.

4) Kill switches and circuit breakers

For any operation that touches billing, emails, or external systems, I add a kill switch. Often this is just an env var read at runtime or a "maintenance mode" flag in Redis.

Example kill switch in bash for deployments:

# deploy.sh
if [ "$(redis-cli GET maintenance_mode)" = "on" ]; then
  echo "Maintenance mode enabled. Aborting."
  exit 1
fi

# roll back
redis-cli SET maintenance_mode on
# roll back quickly if needed

You can't ignore compliance entirely. I do a minimal legal checklist before inviting real users:

  • Clear privacy text on the signup page (one paragraph)
  • Opt-in for emails and billing
  • No scraping of 3rd-party private data
  • A GDPR-style data export/delete endpoint (even if manual behind the scenes)

A small, clear data deletion endpoint buys you a lot of trust.

6) Instrumentation and observability

If you can't measure whether the bet is winning, you can't learn. My baseline:

  • Errors: Sentry (or similar) for stack traces
  • Metrics: Prometheus metrics or simple events to a data warehouse
  • Business events: signup, activation, upgrade, churn to Segment or a CSV log
  • Logs: structured logs to a file or LogDNA/Splunk

I aim to know within 10 minutes if something breaks or conversion drops.

A simple event emitter (pseudo):

emit('signup', { userId, plan });
emit('checkout.success', { userId, amount_cents });

Hook these to a lightweight consumer that writes to a Postgres table for quick analysis.

7) Small deploys, automated rollbacks

Deploy small and often. Each deploy should be revertable with a single command. I use systemd + docker-compose for my VPSes:

git push production main
ssh prod 'cd /srv/app && docker-compose pull && docker-compose up -d'
# rollback
ssh prod 'cd /srv/app && git checkout HEAD~1 && docker-compose up -d'

On more modern infra: kubectl rollout undo deployment/my-app is your friend.

Story: How I shipped a payments MVP in 3 weeks

Quick anecdote to make this concrete.

Goal: Sell a single pro feature behind a paywall and validate willingness-to-pay.

Week 1:

  • Built a minimal UI and checkout flow using Stripe Checkout (I did not implement my own PCI flow).
  • Feature behind a flag; only available to invite users.
  • Added a kill switch and a "payment grace" toggle to disable upgrades.

Week 2:

  • Launched to 120 invites over email. Instrumented signups, trial starts, and successful charges.
  • Fixed a bug exposed by real users: an edge case with phone numbers. Because the feature was flag-protected, only 8 users hit it, and it was easy to patch.

Results:

  • Day 2 revenue: $320 (12% conversion of trial-to-paid in the first cohort).
  • Day 7 churn: 0 for early cohort (small sample).
  • Decision point at day 14: increase invites if metrics stayed healthy. They did, so I opened to 1,000 users with a gradual ramp.

If I'd waited for "formal sign-off" for the billing flow, I would have spent 6 more weeks. Instead I learned the pricing was acceptable and had real revenue before investing more.

When you shouldn't launch without permission

There are clear red lines:

  • Major security changes that affect other customers
  • Permanent migrations with no rollback
  • Anything that violates law or contract (e.g., user data fixes that expose private data)
  • Large infra changes that risk losing data

If a change touches those areas, get the necessary approvals.

Conclusion: Practical takeaways

Launching without permission isn't reckless — it's disciplined. Ship small, make reversible bets, and instrument heavily. Here are the concrete steps to follow the next time you need to move fast:

  • Decide if the change is a reversible bet. If not, get sign-off.
  • Gate the new feature behind a flag and an invite-only rollout.
  • Use non-destructive schema changes and background backfills.
  • Add kill switches for billing, email, and external integrations.
  • Instrument business events (signup, activation, purchase) before you invite users.
  • Start with 50–200 users, learn, then scale the invite list.
  • Automate quick rollback in your deployment process.

If you're shipping alone or with a tiny team, these patterns let you get honest feedback without turning your launch into a crisis. You learn faster, take smaller risks, and keep the option to undo changes at your fingertips.

If you want, I can share the small set of scripts and migration templates I use for these launches — drop a comment or send me an email and I'll open-source the repo.