[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"i-lucide:sun":3,"i-lucide:menu":8,"i-lucide:mail":10,"i-lucide:phone":12,"i-lucide:calendar":14,"i-lucide:map-pin":16,"post-the-launch-checklist-nobody-sends-you":18,"post-html-the-launch-checklist-nobody-sends-you":30,"post-siblings-the-launch-checklist-nobody-sends-you":61,"i-lucide:arrow-left":82,"i-lucide:linkedin":84,"i-lucide:twitter":87,"i-lucide:link":89,"i-lucide:chevron-down":91},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"4\"\u002F>\u003Cpath d=\"M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M2 12h2m16 0h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 5h16M4 12h16M4 19h16\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":11},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"m22 7l-8.991 5.727a2 2 0 0 1-2.009 0L2 7\"\u002F>\u003Crect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":13},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233a14 14 0 0 0 6.392 6.384\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":15},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M8 2v4m8-4v4\"\u002F>\u003Crect width=\"18\" height=\"18\" x=\"3\" y=\"4\" rx=\"2\"\u002F>\u003Cpath d=\"M3 10h18\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":17},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0\"\u002F>\u003Ccircle cx=\"12\" cy=\"10\" r=\"3\"\u002F>\u003C\u002Fg>",{"slug":19,"title":20,"excerpt":21,"body":22,"cover":23,"tags":24,"publishedAt":27,"readingTime":28,"seoTitle":20,"seoDescription":29},"the-launch-checklist-nobody-sends-you","The launch checklist nobody sends you","Most of what goes wrong at launch does not throw an error. The site loads, the pages render, and something underneath quietly is not doing its job.","## Launches fail quietly\n\nMost of what goes wrong at launch does not throw an error. The site loads, the pages render, and something underneath is not doing its job. Nobody notices for weeks, and by then the traffic that mattered has been and gone.\n\nHere is what we check before we call a site live, and why each one earned its place.\n\n## Mail authentication, before anything sends\n\nA domain that sends mail needs three DNS records: SPF, DKIM, and DMARC. Without them, transactional mail from your own domain lands in spam or nowhere.\n\nThe one that catches people is the interaction between them. Point your MX records at one provider and your SPF record at another and you have a domain that receives mail perfectly well and cannot send it credibly. We check that SPF names the service that is actually sending today, not the one that was sending last quarter.\n\n## Backups, with a restore you have actually done\n\nAn automated backup nobody has restored from is a hypothesis. The check is not whether backups run. It is whether somebody has taken one, restored it somewhere else, and looked at the data.\n\nThis gets more important the moment a system holds invoices, payments, or client history rather than marketing copy.\n\n## Security headers on the response, not in the config\n\nA Content Security Policy in a config file is a plan. A Content Security Policy on the response is a policy. We fetch the deployed site and read the headers back, because build-time configuration reaches production through several layers and any of them can drop it.\n\n## Analytics that are actually recording\n\nThis one is quiet in an unusual way. An analytics script can load, define its global, log nothing you would notice, and record not a single page view, because the beacon goes to a different host from the script and the policy above allows only one of the two.\n\nThe check is not that the script tag is present. It is that a page view arrives in the dashboard.\n\n## The sitemap a crawler actually receives\n\nAsk for the URL your robots.txt advertises, and read what comes back. A sitemap route that redirects, or returns HTML, or splits into an index for a site with one language, is a sitemap a crawler has to work to use.\n\n## Every outbound link you published\n\nSites link to products, subdomains, and partner pages that were live when the copy was written. We fetch all of them. A link to a subdomain with no DNS is worse than no link, especially on the page asking somebody to buy something.\n\n## The form, end to end, with a real message\n\nNot a unit test. Fill in the form on the deployed site, send it, and confirm it arrives where a human will read it.\n\nA contact form that shows a success message and drops the enquiry is the most expensive bug a marketing site can have, because the sender believes they have reached you and stops trying. You never find out. It is the one failure that costs revenue without producing a single error log.\n\n## Why we automate most of it\n\nEverything above except the backup restore and the real enquiry runs as a script against the deployed URL. Around fifty checks, a couple of minutes, and it runs after every deploy rather than once on launch day.\n\nA checklist in a document is a thing somebody remembers to read. A checklist the deploy runs is a thing that gets read on the day everybody is in a hurry, which is the day it matters.",null,[25,26],"saas","devops","2026-07-31T08:00:00+03:00",3,"Mail authentication, backups you have restored, headers on the response, analytics that record, and a form tested end to end. What we check before calling a site live.",{"html":31,"toc":32},"\u003Ch2 id=\"launches-fail-quietly\">Launches fail quietly\u003C\u002Fh2>\n\u003Cp>Most of what goes wrong at launch does not throw an error. The site loads, the pages render, and something underneath is not doing its job. Nobody notices for weeks, and by then the traffic that mattered has been and gone.\u003C\u002Fp>\n\u003Cp>Here is what we check before we call a site live, and why each one earned its place.\u003C\u002Fp>\n\u003Ch2 id=\"mail-authentication-before-anything-sends\">Mail authentication, before anything sends\u003C\u002Fh2>\n\u003Cp>A domain that sends mail needs three DNS records: SPF, DKIM, and DMARC. Without them, transactional mail from your own domain lands in spam or nowhere.\u003C\u002Fp>\n\u003Cp>The one that catches people is the interaction between them. Point your MX records at one provider and your SPF record at another and you have a domain that receives mail perfectly well and cannot send it credibly. We check that SPF names the service that is actually sending today, not the one that was sending last quarter.\u003C\u002Fp>\n\u003Ch2 id=\"backups-with-a-restore-you-have-actually-done\">Backups, with a restore you have actually done\u003C\u002Fh2>\n\u003Cp>An automated backup nobody has restored from is a hypothesis. The check is not whether backups run. It is whether somebody has taken one, restored it somewhere else, and looked at the data.\u003C\u002Fp>\n\u003Cp>This gets more important the moment a system holds invoices, payments, or client history rather than marketing copy.\u003C\u002Fp>\n\u003Ch2 id=\"security-headers-on-the-response-not-in-the-config\">Security headers on the response, not in the config\u003C\u002Fh2>\n\u003Cp>A Content Security Policy in a config file is a plan. A Content Security Policy on the response is a policy. We fetch the deployed site and read the headers back, because build-time configuration reaches production through several layers and any of them can drop it.\u003C\u002Fp>\n\u003Ch2 id=\"analytics-that-are-actually-recording\">Analytics that are actually recording\u003C\u002Fh2>\n\u003Cp>This one is quiet in an unusual way. An analytics script can load, define its global, log nothing you would notice, and record not a single page view, because the beacon goes to a different host from the script and the policy above allows only one of the two.\u003C\u002Fp>\n\u003Cp>The check is not that the script tag is present. It is that a page view arrives in the dashboard.\u003C\u002Fp>\n\u003Ch2 id=\"the-sitemap-a-crawler-actually-receives\">The sitemap a crawler actually receives\u003C\u002Fh2>\n\u003Cp>Ask for the URL your robots.txt advertises, and read what comes back. A sitemap route that redirects, or returns HTML, or splits into an index for a site with one language, is a sitemap a crawler has to work to use.\u003C\u002Fp>\n\u003Ch2 id=\"every-outbound-link-you-published\">Every outbound link you published\u003C\u002Fh2>\n\u003Cp>Sites link to products, subdomains, and partner pages that were live when the copy was written. We fetch all of them. A link to a subdomain with no DNS is worse than no link, especially on the page asking somebody to buy something.\u003C\u002Fp>\n\u003Ch2 id=\"the-form-end-to-end-with-a-real-message\">The form, end to end, with a real message\u003C\u002Fh2>\n\u003Cp>Not a unit test. Fill in the form on the deployed site, send it, and confirm it arrives where a human will read it.\u003C\u002Fp>\n\u003Cp>A contact form that shows a success message and drops the enquiry is the most expensive bug a marketing site can have, because the sender believes they have reached you and stops trying. You never find out. It is the one failure that costs revenue without producing a single error log.\u003C\u002Fp>\n\u003Ch2 id=\"why-we-automate-most-of-it\">Why we automate most of it\u003C\u002Fh2>\n\u003Cp>Everything above except the backup restore and the real enquiry runs as a script against the deployed URL. Around fifty checks, a couple of minutes, and it runs after every deploy rather than once on launch day.\u003C\u002Fp>\n\u003Cp>A checklist in a document is a thing somebody remembers to read. A checklist the deploy runs is a thing that gets read on the day everybody is in a hurry, which is the day it matters.\u003C\u002Fp>\n",[33,37,40,43,46,49,52,55,58],{"id":34,"text":35,"level":36},"launches-fail-quietly","Launches fail quietly",2,{"id":38,"text":39,"level":36},"mail-authentication-before-anything-sends","Mail authentication, before anything sends",{"id":41,"text":42,"level":36},"backups-with-a-restore-you-have-actually-done","Backups, with a restore you have actually done",{"id":44,"text":45,"level":36},"security-headers-on-the-response-not-in-the-config","Security headers on the response, not in the config",{"id":47,"text":48,"level":36},"analytics-that-are-actually-recording","Analytics that are actually recording",{"id":50,"text":51,"level":36},"the-sitemap-a-crawler-actually-receives","The sitemap a crawler actually receives",{"id":53,"text":54,"level":36},"every-outbound-link-you-published","Every outbound link you published",{"id":56,"text":57,"level":36},"the-form-end-to-end-with-a-real-message","The form, end to end, with a real message",{"id":59,"text":60,"level":36},"why-we-automate-most-of-it","Why we automate most of it",[62,64,73],{"slug":19,"title":20,"excerpt":21,"body":22,"cover":23,"tags":63,"publishedAt":27,"readingTime":28,"seoTitle":20,"seoDescription":29},[25,26],{"slug":65,"title":66,"excerpt":67,"body":68,"cover":23,"tags":69,"publishedAt":71,"readingTime":28,"seoTitle":66,"seoDescription":72},"what-a-scoping-call-is-for","What a scoping call is for","A scoping call is not a sales call with a nicer name. It exists to find out whether the thing you want built is the thing you need, and whether we are the ones to build it.","# What a scoping call is for\n\nWe ask every enquiry to start with a scoping call, and people reasonably assume that is a sales meeting wearing a polite hat. It is not. It is the cheapest hour in the whole engagement, and it is where most of the money is either saved or lost.\n\n## Three questions, in order\n\n**What breaks if this does not get built?** Not what the software does. What happens to the business without it. If the honest answer is \"not much yet\", that is worth finding out before anyone writes a proposal. Some of the most useful calls we have had ended with us saying the problem was a spreadsheet and a process change, not a product.\n\n**Where is it now?** Greenfield, half-built, or inherited. Each has a completely different first month. Inherited systems in particular need a read of the code before anyone can quote honestly, because the gap between \"it mostly works\" and what is actually in the repository is where fixed-price projects go to die.\n\n**Who owns it after we hand it over?** This changes the architecture. A team with a Laravel developer gets a different handover from a founder who will be the only person with the keys. Building the second one as though it were the first is how a client ends up locked into their own vendor.\n\n## What we produce afterwards\n\nWithin a day or two you get back a written summary: what we understood the problem to be, what we would build first, what we would deliberately leave out of phase one, and the shape of the commercial arrangement. Whether it is a fixed scope or a rate depends on how much of the above is actually known.\n\nIf the answer is that the work is not a fit for us, you get that in writing too, with a reason. An engagement that should not have started is more expensive than one that never did.\n\n## Why we do not quote from a brief alone\n\nA written brief describes a solution somebody has already chosen. Quoting against it means pricing that choice without ever testing it, and if the choice is wrong, the estimate is precise and useless.\n\nWe would rather spend an hour understanding the problem and be slower to send a number.\n\n## What to bring\n\nNothing formal. If you have a document, send it. If you have a spreadsheet holding the business together, that is more useful than a document. Screens of the existing system, a rough sense of budget, and the date something has to be live by are enough to make the hour count.\n",[25,70],"scoping","2026-07-27T08:00:00+03:00","A scoping call is not a sales call. It exists to test whether the thing you want built is the thing you need, and to produce a written summary you can act on.",{"slug":74,"title":75,"excerpt":76,"body":77,"cover":23,"tags":78,"publishedAt":80,"readingTime":36,"seoTitle":75,"seoDescription":81},"what-running-saas-actually-costs","What running SaaS actually costs after launch","Most SaaS budgets stop at launch. The bill that follows is patching, backups, monitoring, and the on-call hour nobody planned for.","# What running SaaS actually costs after launch\n\nLaunch is the cheap part. It has a scope, a date, and an invoice. What follows has none of those, which is why it gets left out of the budget.\n\n## The costs that arrive after launch\n\nA running SaaS product needs, every month, whether or not anyone ships a feature:\n\n- **Dependency updates.** A Laravel and Vue application pulls hundreds of packages. Left alone for a year, upgrading stops being a chore and becomes a project.\n- **Security patching.** CVEs do not wait for a convenient sprint.\n- **Backups you have actually restored.** An untested backup is a hope, not a backup.\n- **Monitoring and alerting.** Knowing a queue has stalled before a customer tells you.\n- **On-call time.** Someone has to be reachable when a payment webhook starts failing at 9pm.\n\nNone of this is visible in a demo. All of it is the difference between software that runs for five years and software that quietly rots for eighteen months and then needs rewriting.\n\n## Why we price maintenance separately\n\nWe put support and SLA work on its own line because bundling it hides it. When maintenance is folded into a build price, the first thing cut under pressure is the invisible half. Then the rewrite lands two years later and costs more than the original build.\n\nA separate line is harder to sell and easier to defend.\n\n## What we do about it\n\nEvery system we hand over ships with the operational parts already in place:\n\n```\nCI on every push          tests + build must pass before deploy\nStaging that mirrors prod  same runtime, same migrations\nAutomated backups          with a documented restore, tested\nUptime + error monitoring  alerts to a human, not a dashboard\n```\n\nThat is not a premium tier. It is what \"we run it\" means.\n\n## The question to ask a vendor\n\nNot \"what will it cost to build\". Ask what it costs to keep running in year two, who is on call, and what happens when they are not available. A vendor who has not thought about it will improvise an answer, and the improvisation is the answer.\n",[25,26,79],"maintenance","2026-07-20T08:00:00+03:00","Dependency updates, security patching, tested backups, monitoring, and on-call time. The post-launch bill that most SaaS budgets leave out.",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":83},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m12 19l-7-7l7-7m7 7H5\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":85,"hidden":86},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2a2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6M2 9h4v12H2z\"\u002F>\u003Ccircle cx=\"4\" cy=\"4\" r=\"2\"\u002F>\u003C\u002Fg>",true,{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":88,"hidden":86},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6c2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4c-.9-4.2 4-6.6 7-3.8c1.1 0 3-1.2 3-1.2\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":90},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"\u002F>\u003Cpath d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":92},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m6 9l6 6l6-6\"\u002F>"]