ThreadCount Community edition
Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
/* Render docs/launch/one-pager.html to an A4 PDF.
|
||||
*
|
||||
* node docs/launch/build-one-pager.cjs [output.pdf]
|
||||
*
|
||||
* It asserts the result is a single page, because a one-pager that quietly becomes two is worse
|
||||
* than useless — the second sheet is where the contact details end up, and nobody prints it.
|
||||
*/
|
||||
const { chromium } = require('playwright');
|
||||
const path = require('path');
|
||||
|
||||
const SRC = path.join(__dirname, 'one-pager.html');
|
||||
const OUT = process.argv[2] || path.join(process.env.HOME || '.', 'Downloads', 'ThreadCount-one-pager.pdf');
|
||||
const A4_PX = Math.round((297 / 25.4) * 96); // A4 height at CSS 96dpi
|
||||
|
||||
(async () => {
|
||||
const b = await chromium.launch();
|
||||
const p = await b.newPage();
|
||||
await p.goto('file://' + SRC, { waitUntil: 'networkidle' });
|
||||
await p.evaluate(() => document.fonts.ready);
|
||||
await p.waitForTimeout(800); // let the webfont settle before measuring
|
||||
const h = await p.evaluate(() => document.body.getBoundingClientRect().height);
|
||||
await p.pdf({ path: OUT, format: 'A4', printBackground: true, margin: { top: 0, right: 0, bottom: 0, left: 0 } });
|
||||
await b.close();
|
||||
console.log(`content ${Math.round(h)}px against an A4 page of ${A4_PX}px`);
|
||||
if (h > A4_PX + 1) {
|
||||
console.error('SPILLS ONTO A SECOND PAGE — trim the content or the block spacing, not the type size.');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('one page ->', OUT);
|
||||
})();
|
||||
@@ -0,0 +1,59 @@
|
||||
# Marketing copy: what I changed, and what I deliberately didn't
|
||||
|
||||
You asked for a rewrite of the marketing copy for launch. Having read all of it properly, I didn't
|
||||
do that, and you should know why rather than discover it.
|
||||
|
||||
## Why not a rewrite
|
||||
|
||||
The copy is already doing the hard thing. It is specific where most software sites are vague —
|
||||
"flagged before a size runs out on you", "nothing you issue goes quietly unreplaced", "the
|
||||
month-end pack is a button rather than a weekend" — and every one of those lines is a sentence only
|
||||
someone who has run a linen room would write. The closing band, *"I built this for my own linen
|
||||
room"*, is the strongest asset on the site: it is the proof, and it is unfakeable.
|
||||
|
||||
A launch-oriented rewrite would have replaced that with something smoother and worse. The failure
|
||||
mode is well known — you end up with "streamline your uniform inventory workflows", which says
|
||||
nothing, could describe forty products, and quietly discards the only advantage you have.
|
||||
|
||||
So the change is targeted rather than wholesale.
|
||||
|
||||
## What I changed
|
||||
|
||||
- **The guides are surfaced on the home page.** A new band between the product screen and the
|
||||
closing call to action. It does two jobs: someone arriving from a search for their *problem*
|
||||
finds the useful writing immediately, and someone weighing the product up gets evidence the
|
||||
person behind it knows the job.
|
||||
|
||||
## What I looked at and left alone
|
||||
|
||||
- **The hero.** "Every garment out the door, accounted for." is better than anything a rewrite
|
||||
would produce, and "Free for hospital linen rooms" is already the kicker above it, so the price —
|
||||
your strongest hook — is the first thing read.
|
||||
- **Calls to action.** Hero leads with the demo rather than sign-up. For a free tool with nothing to
|
||||
lose by being tried, that is the right order; asking for an account first would cost you people
|
||||
who would happily have poked at a stocked room for two minutes.
|
||||
- **The absence of social proof.** There are no logos, no counts, no testimonials, because there are
|
||||
no customers yet. Leave it that way. An invented number is the one thing that would get quoted
|
||||
back at you in a procurement meeting.
|
||||
|
||||
## Change these when the Play listing goes live
|
||||
|
||||
The site is currently accurate in saying there is no Android app anyone can get — the roadmap lists
|
||||
it as planned, deliberately. The moment the listing is public, three things become stale:
|
||||
|
||||
1. **`/roadmap`** — "Android app" moves out of Planned. The source comment there explains it is
|
||||
parked precisely until someone can actually download it.
|
||||
2. **`/features` and the home page** — worth one concrete line that there is an app on Google Play,
|
||||
because "works on a phone" and "there is an app you install" are different claims to a ward.
|
||||
3. **`docs/launch/outreach.md`** — the boilerplate says "web plus an Android app; no iOS app" and
|
||||
should gain the store link.
|
||||
|
||||
Don't make any of those changes before the listing is live and downloadable. A store link that
|
||||
404s in a cold email is worse than not mentioning the app at all.
|
||||
|
||||
## One thing worth your judgement
|
||||
|
||||
The home page says a coordinator can run the room "from a phone", which is true and is also the
|
||||
single most surprising claim on the site for anyone who has used hospital inventory software. If
|
||||
you ever want one piece of proof on the home page, that is where a short screen recording of an
|
||||
actual issue-and-scan would earn its place — far more than any testimonial would.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Where to list ThreadCount
|
||||
|
||||
A caution before the list: **verify each destination before submitting.** Submission pages,
|
||||
category names and even whether a directory still accepts entries change often, and I have
|
||||
deliberately not written deep links I cannot vouch for. What follows is where to look and what to
|
||||
say, not a set of URLs to paste blindly.
|
||||
|
||||
Copy every description from the boilerplate in `outreach.md` rather than rewriting per site.
|
||||
Identical wording across listings is worth more than variety: it is what makes the same product
|
||||
recognisable, and search engines treat consistent name/description/URL as a coherence signal.
|
||||
|
||||
## Do these first
|
||||
|
||||
| Destination | Why it's worth it | Notes |
|
||||
|---|---|---|
|
||||
| **Google Business Profile** | The only listing likely to affect what appears when someone searches the product name | Software with no physical premises is an awkward fit; if a service-area profile isn't appropriate, skip rather than fudge an address |
|
||||
| **Google Search Console** | Not a directory — the thing that tells you whether any of this worked | Verify the domain, submit `https://threadcount.tech/sitemap.xml`, then leave it alone for a few weeks |
|
||||
| **Bing Webmaster Tools** | Small share, near-zero effort, and it feeds other engines | Imports directly from Search Console |
|
||||
| **Google Play listing** | Already done, but it is a real discovery surface | Keep the short description identical to the boilerplate one-liner |
|
||||
|
||||
## Software directories
|
||||
|
||||
These carry real referral traffic for niche B2B tools and, more usefully, they rank for
|
||||
"«category» software" searches you will never outrank yourself.
|
||||
|
||||
| Destination | Notes |
|
||||
|---|---|
|
||||
| **Capterra / GetApp / Software Advice** | Same parent company; one submission usually feeds all three. Free products are accepted. Expect a sales call — decline it |
|
||||
| **G2** | Listing is free. Reviews drive everything, and you have no customers yet, so treat this as a placeholder to claim now and populate later |
|
||||
| **AlternativeTo** | Good fit for a free tool. Position it against manual spreadsheets rather than against enterprise suites |
|
||||
| **SourceForge / Slashdot software listings** | Low effort, accepts free tools |
|
||||
| **Product Hunt** | One shot, and only worth taking when you actively want the traffic. A free niche healthcare tool can do well, but launch on a weekday and be present to answer comments all day |
|
||||
|
||||
## Australian health and digital health
|
||||
|
||||
Worth more than the generic directories for this product, because the audience is exactly right —
|
||||
but most are membership bodies or ecosystem maps rather than software listings, so read what they
|
||||
actually publish before submitting.
|
||||
|
||||
| Destination | Notes |
|
||||
|---|---|
|
||||
| **Australasian Institute of Digital Health (AIDH)** | The main professional body; has member directories and community channels. Membership may be required to be listed |
|
||||
| **ANDHealth** | Digital health commercialisation body. Better as a relationship than a listing |
|
||||
| **Digital Health CRC** | Research/industry network; worth knowing, less useful as a directory |
|
||||
| **State health service innovation hubs** | Most jurisdictions run one. These are the likeliest route to a real conversation with a facility, and far more valuable than any directory |
|
||||
| **Hospital and health service procurement / innovation newsletters** | Often accept short "free tool" notices. Ask before submitting |
|
||||
|
||||
## Not worth it
|
||||
|
||||
- **Paid directory placements.** At $1,290 a facility a year, one placement would need several
|
||||
sign-ups to pay for itself, and none of these directories is where a linen room looks.
|
||||
- **Mass submission services.** They generate low-quality backlinks that are at best ignored.
|
||||
- **Anything requiring an invented company size, funding stage or customer count.** Leave the field
|
||||
blank or skip the listing. A fabricated figure in a directory is the one that gets quoted back at
|
||||
you in a procurement meeting.
|
||||
|
||||
## Order of operations
|
||||
|
||||
1. Search Console + Bing, and confirm the sitemap is being read.
|
||||
2. Boilerplate finalised in `outreach.md` — everything else copies from it.
|
||||
3. Capterra/GetApp, AlternativeTo, G2 placeholder.
|
||||
4. AIDH and the relevant state innovation hub, as conversations rather than form submissions.
|
||||
5. Product Hunt only when you want a traffic spike and can sit with it for a day.
|
||||
|
||||
## What to measure
|
||||
|
||||
All of it lands in Umami at `analytics.threadcount.tech` under the **ThreadCount** site. Referrer is
|
||||
recorded, so a listing that sends real people is visible within a day of sending any. The signal
|
||||
worth watching is not visits — it is whether `/demo` gets opened and whether `signup_completed`
|
||||
fires, both of which are already tracked as events.
|
||||
@@ -0,0 +1,129 @@
|
||||
<!doctype html>
|
||||
<html lang="en-AU">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ThreadCount — one page</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;600;700;800&display=swap">
|
||||
<style>
|
||||
/* A4, printed. Sized to fit one page with room for a printer's margins, because the whole point
|
||||
of a one-pager is that it is one page — a second sheet is a document nobody reads. */
|
||||
@page { size: A4; margin: 0; }
|
||||
:root {
|
||||
--ink: #201e1d;
|
||||
--paper: #f3f2f2;
|
||||
--accent: #ec3013;
|
||||
--accent-700: #c4260f;
|
||||
--grey: #6b6764;
|
||||
--rule: #d6d3d1;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; background: #fff; }
|
||||
body {
|
||||
font-family: Archivo, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
color: var(--ink);
|
||||
width: 210mm; min-height: 297mm;
|
||||
padding: 12mm 15mm 10mm;
|
||||
-webkit-print-color-adjust: exact; print-color-adjust: exact;
|
||||
}
|
||||
h1, h2, h3 { margin: 0; letter-spacing: -0.02em; }
|
||||
p { margin: 0; }
|
||||
|
||||
.top { display: flex; align-items: center; justify-content: space-between; border-bottom: 2px solid var(--ink); padding-bottom: 7mm; }
|
||||
.mark { display: flex; align-items: center; gap: 3mm; }
|
||||
.mark i { width: 5mm; height: 5mm; background: var(--accent); display: block; }
|
||||
.mark span { font-weight: 800; font-size: 15pt; letter-spacing: -0.01em; }
|
||||
.free { border: 2px solid var(--ink); padding: 2mm 3mm; font-size: 7.5pt; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; }
|
||||
|
||||
.lead { margin-top: 6mm; }
|
||||
.kicker { font-size: 7.5pt; font-weight: 800; letter-spacing: 0.15em; text-transform: uppercase; color: var(--accent-700); }
|
||||
h1 { font-size: 27pt; line-height: 1.02; font-weight: 800; margin-top: 3mm; max-width: 15cm; }
|
||||
.lede { font-size: 10.5pt; line-height: 1.5; margin-top: 4mm; max-width: 15.5cm; color: #34302e; }
|
||||
.rule { width: 22mm; height: 1.6mm; background: var(--accent); margin-top: 5mm; }
|
||||
|
||||
.cols { display: grid; grid-template-columns: 1fr 1fr; column-gap: 10mm; margin-top: 6mm; }
|
||||
.sec { font-size: 7.5pt; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; border-bottom: 2px solid var(--ink); padding-bottom: 2mm; margin-bottom: 3mm; }
|
||||
.item { padding: 2.6mm 0; border-bottom: 1px solid var(--rule); }
|
||||
.item b { display: block; font-size: 10pt; font-weight: 800; }
|
||||
.item span { display: block; font-size: 8.6pt; line-height: 1.42; color: #494340; margin-top: 0.8mm; }
|
||||
|
||||
.band { margin-top: 6mm; background: var(--ink); color: #fff; padding: 5mm 7mm; }
|
||||
.band .sec { border-bottom-color: rgba(255,255,255,.35); color: #fff; }
|
||||
.band .q { font-size: 10.5pt; line-height: 1.5; max-width: 16cm; }
|
||||
.band .q b { color: #fff; }
|
||||
|
||||
.foot { margin-top: 6mm; display: flex; justify-content: space-between; align-items: flex-end; border-top: 2px solid var(--ink); padding-top: 5mm; gap: 8mm; }
|
||||
.foot .facts { font-size: 8.4pt; line-height: 1.5; color: var(--grey); max-width: 10.5cm; }
|
||||
.foot .cta { text-align: right; }
|
||||
.foot .cta b { display: block; font-size: 12pt; font-weight: 800; }
|
||||
.foot .cta span { display: block; font-size: 9pt; color: var(--grey); margin-top: 1mm; }
|
||||
.url { color: var(--accent-700); font-weight: 800; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top">
|
||||
<div class="mark"><i></i><span>ThreadCount</span></div>
|
||||
<div class="free">Free to run</div>
|
||||
</div>
|
||||
|
||||
<div class="lead">
|
||||
<div class="kicker">Uniform management for hospitals, aged care and clinics</div>
|
||||
<h1>Every garment out the door, accounted for.</h1>
|
||||
<p class="lede">
|
||||
ThreadCount follows a garment from the shelf, to the person wearing it, to the supplier order
|
||||
that puts another one back. It was built by a uniform coordinator for their own linen room,
|
||||
and the software is free — run it yourself for nothing, or have it hosted from $1,290 a year
|
||||
with no per-user charge, and nobody will ring you.
|
||||
</p>
|
||||
<div class="rule"></div>
|
||||
</div>
|
||||
|
||||
<div class="cols">
|
||||
<div>
|
||||
<div class="sec">What it does</div>
|
||||
<div class="item"><b>Stock by size, not by product</b><span>On-hand for every size, updated as you issue, flagged before a size runs out.</span></div>
|
||||
<div class="item"><b>Issue to a person</b><span>Entitlement balance on screen, manager approval recorded when someone goes over, credit slip for the balance.</span></div>
|
||||
<div class="item"><b>Scan with a phone</b><span>Reads the supplier barcode already on the garment, from the Android app or Chrome. No scanner hardware to buy.</span></div>
|
||||
<div class="item"><b>Count a shelf at a time</b><span>Expected figure in hand as you count, and a reason captured for any real variance.</span></div>
|
||||
<div class="item"><b>Orders that build themselves</b><span>Every issue feeds a draft supplier order. Shorts split to a back order automatically.</span></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec">What it answers</div>
|
||||
<div class="item"><b>“How many size M do we have?”</b><span>Per size, per location, right now.</span></div>
|
||||
<div class="item"><b>“Who has that garment?”</b><span>Issued to a named person, with what they still hold.</span></div>
|
||||
<div class="item"><b>“Why did Willow Ward double?”</b><span>Spend attributed to the ward that incurred it, drillable to the issues behind it.</span></div>
|
||||
<div class="item"><b>“What do we owe them?”</b><span>Entitlement, what has been taken, and the balance outstanding.</span></div>
|
||||
<div class="item"><b>“Where did it go?”</b><span>Leavers still holding stock, size changes, damage written off as damage.</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="band">
|
||||
<div class="sec">For finance</div>
|
||||
<p class="q">
|
||||
Uniform spend attributes to the cost centre on the staff record and values at unit cost, so
|
||||
the month-end job is an export rather than an investigation. The journal comes out as one
|
||||
debit line per cost centre, as CSV, for the month you choose — and every line drills back to
|
||||
the issues behind it when a ward manager queries their number.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="foot">
|
||||
<div class="facts">
|
||||
Australian hosted and kept separate per facility; only the accounts you invite can see your
|
||||
room’s records. Works in any browser, with an Android app for the ward. Every report and
|
||||
register exports as CSV. One backup file takes the whole facility: nothing to cancel,
|
||||
nothing held back.
|
||||
<br><br>
|
||||
Worth sending to your information security team early: <span class="url">threadcount.tech/security</span>
|
||||
</div>
|
||||
<div class="cta">
|
||||
<b>Try it without signing up</b>
|
||||
<span class="url">threadcount.tech/demo</span>
|
||||
<span>hello@threadcount.tech</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,177 @@
|
||||
# Outreach pack
|
||||
|
||||
Everything here is written to be sent by one person who runs a linen room to another person who
|
||||
runs a linen room. That is ThreadCount's only real advantage in a market full of enterprise
|
||||
inventory suites, and the moment the writing sounds like a vendor it is gone.
|
||||
|
||||
Three rules that shaped all of it:
|
||||
|
||||
- **Lead with the problem, not the product.** A linen services manager does not want software. They
|
||||
want to stop being asked why Willow Ward's uniform spend doubled.
|
||||
- **The price is the hook, and it is not "cheap".** The software is free, a small room is hosted
|
||||
free, and a hospital pays $1,290 a year for hosting — say it plainly and early, because a number
|
||||
a linen room can approve itself is the single fact that gets a reply.
|
||||
- **Never claim a customer you don't have.** No "trusted by", no invented logos, no fake numbers. A
|
||||
first-mover asking someone to be an early site is an honest and appealing position; pretending to
|
||||
be established is neither, and it collapses on the first question.
|
||||
|
||||
---
|
||||
|
||||
## 1. Cold email — to a linen services / uniform coordinator
|
||||
|
||||
Subject lines, in rough order of how well they tend to travel. Keep them lower-case-ish and
|
||||
specific; anything that reads like a campaign gets filtered.
|
||||
|
||||
- `Uniform stock — something I built for my own linen room`
|
||||
- `A tool for uniform issuing and stocktakes, free to run`
|
||||
- `A question about how you track uniform issues`
|
||||
|
||||
Body:
|
||||
|
||||
> Hi {first name},
|
||||
>
|
||||
> I coordinate uniforms for a hospital linen room, and I got tired of not being able to answer two
|
||||
> questions: what's actually on the shelf in each size, and which ward a garment was issued to.
|
||||
>
|
||||
> So I built something to do it — issuing against entitlement, barcode scanning on a phone,
|
||||
> stocktakes by shelf, and a cost centre export finance will take. It's called ThreadCount and it's
|
||||
> free to run yourself, free hosted for a small room, and $1,290 a year hosted for a facility our
|
||||
> size. No per-user charge, no sales process; I'm not trying to build a company out of it.
|
||||
>
|
||||
> I'm looking for a couple of other linen rooms to use it properly and tell me where it falls over.
|
||||
> If that sounds useful, the demo is a stocked room you can poke at without signing up:
|
||||
> https://threadcount.tech/demo
|
||||
>
|
||||
> If it's not for you, no follow-up — I won't email again.
|
||||
>
|
||||
> {name}
|
||||
|
||||
**Why it's shaped like that:** it opens with a job title rather than a pitch, states the two
|
||||
questions any coordinator recognises, gives the price in the third paragraph where it lands as
|
||||
relief rather than a gimmick, and closes with an explicit promise not to chase. That last line
|
||||
costs nothing and materially raises reply rates, because the reader stops weighing whether
|
||||
answering commits them to anything.
|
||||
|
||||
**Do not** add a follow-up sequence to this. The site tells people there isn't one.
|
||||
|
||||
---
|
||||
|
||||
## 2. Warm intro — when someone offers to pass it on
|
||||
|
||||
Short enough to be forwarded without editing.
|
||||
|
||||
> ThreadCount is a tool for hospitals, aged care and clinics — free to run, hosted from $1,290 a year — uniform stock by size, issuing against
|
||||
> entitlement with manager approvals, barcode scanning on a phone, stocktakes, and a cost centre
|
||||
> export for finance. Built by a uniform coordinator for their own room, not by a vendor.
|
||||
>
|
||||
> Demo, no sign-up: https://threadcount.tech/demo
|
||||
|
||||
---
|
||||
|
||||
## 3. Reply templates
|
||||
|
||||
**"What does it cost?"**
|
||||
|
||||
> The software is free — run it on your own server and there is nothing to pay. Hosted on
|
||||
> threadcount.tech it is free for a room under 60 staff records, and $1,290 a year for a facility
|
||||
> past that, which covers the hosting, 35 days of backups and me answering email the next business
|
||||
> day. No per-device or per-user charge, and every feature is in every edition. Health services
|
||||
> with several sites: $4,990 a year for five, with one sign-in across them.
|
||||
|
||||
**"Who else uses it?"**
|
||||
|
||||
> Honestly — it's early, and I'd rather say that than pad a list. It runs a real linen room every
|
||||
> day, which is mine. That's the trade: you'd be early enough to shape it, and I'm around to fix
|
||||
> things quickly.
|
||||
|
||||
**"What about our data / IT security?"**
|
||||
|
||||
> Fair question and the right one to ask before real staff data goes in. Everything is Australian
|
||||
> hosted, kept separate per facility, and only visible to the accounts you invite. There's a plain
|
||||
> summary at https://threadcount.tech/security and a full privacy policy at
|
||||
> https://threadcount.tech/privacy — worth sending to your information security team early rather
|
||||
> than late.
|
||||
|
||||
**"Can we get our data out?"**
|
||||
|
||||
> Yes, whenever you want. Every report and register exports as CSV, the ward request queue included.
|
||||
> An admin can also download the whole facility as a single backup file — catalogue, staff,
|
||||
> departments, suppliers, locations, and every issue, order and count. There's nothing to cancel and
|
||||
> nothing held back.
|
||||
|
||||
---
|
||||
|
||||
## 4. Fifteen-minute demo script
|
||||
|
||||
Do not tour the menus. Walk one morning at the counter.
|
||||
|
||||
1. **Set the scene (1 min).** "This is a linen room with a catalogue, a staff register and stock on
|
||||
the shelf. I'll do what a coordinator does before lunch."
|
||||
2. **Issue to someone (3 min).** Scan a garment, pick the staff member. Point out the ward, cost
|
||||
centre and last-issued sizes already on the record, and the entitlement balance on screen. Issue
|
||||
below entitlement so the credit slip appears.
|
||||
3. **The exception (2 min).** Try to go over entitlement. Show that it isn't blocked — it asks for
|
||||
the approval, and records who approved it. This is the moment most coordinators lean in.
|
||||
4. **A size change (1 min).** Exchange a medium for a large as one movement. Note that this is the
|
||||
thing that quietly wrecks per-size numbers when it's done informally.
|
||||
5. **Count a shelf (3 min).** Open a stocktake on the phone, scan a few, show the expected figure
|
||||
on screen, force a variance and let it ask for a reason.
|
||||
6. **What finance gets (3 min).** The cost centre report, one line per ward, and the CSV. Click a
|
||||
ward's code to open the issues behind its figure — that is the answer to the question the whole
|
||||
call opened with. Say plainly that if their finance team needs a different layout, that's a small
|
||||
change.
|
||||
7. **Close (2 min).** "There's a 60-day trial with no card. The demo resets, so if you want to try
|
||||
it properly, set up your own facility — the catalogue and staff register come in from CSV."
|
||||
|
||||
**If time is short**, cut steps 4 and 6. Never cut step 3; the approval flow is what distinguishes
|
||||
this from a spreadsheet.
|
||||
|
||||
---
|
||||
|
||||
## 5. Boilerplate
|
||||
|
||||
Reuse these verbatim so the description is identical everywhere it appears. Consistency across
|
||||
listings is worth more than clever variation.
|
||||
|
||||
**One line (≤ 90 chars)**
|
||||
|
||||
> Uniform stock management for hospitals, aged care and clinics. Free to run, hosted from $1,290 a year.
|
||||
|
||||
**Short (≤ 160 chars — matches the site's meta description)**
|
||||
|
||||
> Uniform stock management for hospitals, aged care and clinics. Track what's on the shelf, who
|
||||
> took it and what it cost the ward.
|
||||
|
||||
**Medium (~60 words)**
|
||||
|
||||
> ThreadCount is uniform stock management for hospitals, aged care and clinics. It follows a garment from
|
||||
> the shelf, to the person wearing it, to the supplier order that puts another one back — issuing
|
||||
> against entitlement with manager approvals, barcode scanning from a phone, stocktakes by shelf,
|
||||
> and cost centre reporting finance will accept. Built by a uniform coordinator for their own room.
|
||||
|
||||
**Long (~120 words)**
|
||||
|
||||
> ThreadCount is uniform stock management for hospitals, aged care and clinics. The software is free
|
||||
> and the code is published; hosting is free for a small room and $1,290 a year for a facility, with
|
||||
> no per-user charge and every feature in every edition.
|
||||
>
|
||||
> It answers the two questions a linen room can rarely answer: what is on the shelf in each size,
|
||||
> and who took it. Garments are issued to a named person against their entitlement, with their
|
||||
> manager's approval recorded where someone goes over. Barcodes are read by the camera on an Android phone,
|
||||
> so a room with a phone in it buys no scanner hardware at all. Stocktakes are counted by shelf with
|
||||
> variance reasons captured while the reason is still known. Uniform spend attributes to the ward
|
||||
> that incurred it and exports as a journal finance can upload.
|
||||
>
|
||||
> Australian hosted, separate per facility, and yours to take away — every report and register
|
||||
> exports as CSV, and the whole facility as one backup file.
|
||||
|
||||
**Category words for listing forms:** inventory management, healthcare, uniform management, asset
|
||||
tracking, stocktake, barcode scanning, hospital operations.
|
||||
|
||||
**Facts that must stay accurate everywhere:** free to run yourself; hosted free under 60 staff
|
||||
records, $1,290 a year per facility above it, $4,990 a year for a health service of five (prices in
|
||||
`lib/plan.ts`); facilities from before plans stay free with everything; Australian hosted; web plus an Android app; no
|
||||
iOS app; camera scanning works in the Android app and in Chrome or Edge, and nowhere else — an
|
||||
iPhone types the code or uses a USB scanner; every report and register exports as CSV, the ward
|
||||
request queue included — it was the last one without one; the whole facility exports as one backup
|
||||
file; there is no sales team.
|
||||
@@ -0,0 +1,66 @@
|
||||
# The notice to existing rooms
|
||||
|
||||
The pricing page promised: *"If that ever changes, the rooms using it will hear before the website
|
||||
does."* This is how they hear. It goes to every active administrator of every facility that exists
|
||||
today, sixty days before the plans switch is turned on, and the website does not change until that
|
||||
day. `scripts/send-plans-notice.cjs` sends it from the production box, and refuses to send without
|
||||
`--send` — run it once without to see who would get it.
|
||||
|
||||
The sixty days are the notice period the new Terms promise for price changes, so the first change
|
||||
is held to the same standard as every later one.
|
||||
|
||||
**Facts the email states, which must be true on the day:**
|
||||
|
||||
- The facility is grandfathered: hosted free, with everything, for as long as it exists.
|
||||
- Nothing needs doing, and nothing on their screens changes.
|
||||
- New facilities from the go-live date start on Hosted Small (free under 60 staff records) or a
|
||||
Hosted Facility trial ($1,290 a year after it). Prices from `lib/plan.ts`.
|
||||
- The source will be published so anyone may run it themselves.
|
||||
- Replies go to hello@threadcount.tech.
|
||||
|
||||
## The email
|
||||
|
||||
**Subject:** ThreadCount is introducing plans on {DATE}. Yours stays free.
|
||||
|
||||
> Hello {FIRST},
|
||||
>
|
||||
> A note from the person who runs ThreadCount, sixty days ahead, because the pricing page said you
|
||||
> would hear before the website did.
|
||||
>
|
||||
> On {DATE} ThreadCount will start charging new facilities for hosting. Nothing changes for
|
||||
> {FACILITY}. You signed up while it was free, and it stays free for you — every feature, every
|
||||
> report, both apps, no ceiling on staff records — for as long as your facility exists. There is
|
||||
> nothing to do, nothing to sign, and nothing on your screens will look different on the day.
|
||||
>
|
||||
> What is changing, for facilities created from {DATE}:
|
||||
>
|
||||
> - The software itself stays free. The code will be published so anyone can run it on their own
|
||||
> server, with everything in it.
|
||||
> - Hosting on threadcount.tech stays free for a room under 60 staff records.
|
||||
> - A larger facility hosted on threadcount.tech will pay $1,290 a year (or $129 a month), which
|
||||
> covers the servers, 35 days of backups, and a person who answers email the next business day.
|
||||
> - Health services running several facilities will be able to buy them together, with one sign-in
|
||||
> across sites.
|
||||
>
|
||||
> The Terms of Service now carry a Fees section that writes the grandfathering down, so it does not
|
||||
> depend on a promise in an email: https://threadcount.tech/terms
|
||||
>
|
||||
> Why now: running it for other people costs money and time, and I would rather charge new rooms
|
||||
> plainly than let the thing quietly stop being maintained. Charging the rooms that trusted it
|
||||
> first was never on the table.
|
||||
>
|
||||
> If you would prefer to pay anyway, or your health service wants the multi-site arrangement, reply
|
||||
> to this email. Otherwise, carry on exactly as you are.
|
||||
>
|
||||
> Kyle
|
||||
> ThreadCount · hello@threadcount.tech
|
||||
|
||||
## After sending
|
||||
|
||||
- The script appends "Plans notice sent {today}, go-live {DATE}" to each facility's plan note, so
|
||||
the operations console shows who was told and when.
|
||||
- Put the go-live date in the calendar. On the day: turn the plans switch on in the operations
|
||||
console (Switches › Plans). The pricing page, the home page, the FAQ, the footer and the sign-up
|
||||
choice all change within a minute; nothing needs deploying.
|
||||
- Update the outreach pack's date placeholders, if any remain, and the Play listings' long
|
||||
description if it says "free" without qualification.
|
||||
@@ -0,0 +1,108 @@
|
||||
# Self-hosting ThreadCount (Community edition)
|
||||
|
||||
The Community edition is the hosted product with `EDITION=community` in its environment. This
|
||||
page says what that changes, how to run it, how to keep it, and what is not included.
|
||||
|
||||
## What `EDITION=community` changes
|
||||
|
||||
| | Hosted (threadcount.tech) | Community |
|
||||
|---|---|---|
|
||||
| Features | All | All |
|
||||
| Plans, staff ceiling, read-only for non-payment | Yes | None. Every facility behaves as a grandfathered one. |
|
||||
| Public website at `/` | The marketing site | Redirects to sign-in |
|
||||
| Demo facility | Yes | Off |
|
||||
| Operations console (`ops.` hostname) | Yes | Off unless `OPS_SESSION_SECRET` and the ops database roles are set up |
|
||||
| Cloudflare Turnstile on sign-in and sign-up | Required | Optional. Set both keys to enforce it; without it the per-address rate limits stand alone. |
|
||||
| Error reports | To ThreadCount's own GlitchTip, scrubbed | Nowhere, unless you set `NEXT_PUBLIC_GLITCHTIP_DSN` to your own |
|
||||
| Usage statistics | To ThreadCount's own Umami, scrubbed | Nowhere. The tracker only mounts on `*.threadcount.tech`. |
|
||||
| Single sign-on for a facility | Via ThreadCount's Jackson broker | Run your own [BoxyHQ Jackson](https://boxyhq.com/docs/jackson/overview) and set `JACKSON_URL` and `JACKSON_API_KEY`; otherwise no SSO |
|
||||
| Transactional email | ThreadCount's SMTP | Your SMTP, or none |
|
||||
|
||||
Everything the legal pages on threadcount.tech say applies to the hosted service. Your instance
|
||||
is yours: publish your own privacy statement for your staff. The pages under `/privacy`,
|
||||
`/terms` and so on still render on a Community instance, but they describe threadcount.tech.
|
||||
|
||||
## Running it
|
||||
|
||||
`docker-compose.yml` runs three services: `db` (Postgres 16, in a named volume), `migrate` (a
|
||||
one-shot `prisma migrate deploy` that runs before the app on every `up`), and `app` (Next.js's
|
||||
standalone server, port 3000, photographs in a second named volume). Put TLS in front of port
|
||||
3000. A minimal Caddyfile:
|
||||
|
||||
```
|
||||
uniforms.example.health {
|
||||
reverse_proxy 127.0.0.1:3000
|
||||
}
|
||||
```
|
||||
|
||||
The image is built on your machine, not pulled, because `NEXT_PUBLIC_SITE_URL` and the optional
|
||||
Turnstile site key are compiled into the browser bundle. If you change either, rebuild:
|
||||
`docker compose up -d --build`.
|
||||
|
||||
### The first facility
|
||||
|
||||
Open the hostname. Create an account; that creates a facility and makes you its administrator.
|
||||
Under Settings, name your staff groups first (nothing works until a facility has them), then load
|
||||
the catalogue, departments, staff register, barcodes and opening stock from CSV under Settings ›
|
||||
Data. Add a second administrator under Settings › Account › Users before you sign out — a
|
||||
forgotten password with one admin and no SMTP locks the facility.
|
||||
|
||||
Then set `SIGNUPS_DISABLED=1` in `.env` and `docker compose up -d`. Your instance is now one
|
||||
facility, and nobody else can create another.
|
||||
|
||||
## Upgrading
|
||||
|
||||
```sh
|
||||
git pull
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
`migrate` applies any new schema migrations before the new app starts; the old app keeps
|
||||
serving until then. Take a backup first (below). Releases that need more than that say so in
|
||||
their notes.
|
||||
|
||||
## Backups
|
||||
|
||||
`docker/backup.sh <directory>` writes one directory per run: the database as a custom-format
|
||||
`pg_dump` and the photographs as a tarball, keeping the last fourteen. Run it nightly from cron:
|
||||
|
||||
```
|
||||
30 3 * * * cd /srv/threadcount && docker/backup.sh /srv/backups/threadcount >> /var/log/threadcount-backup.log 2>&1
|
||||
```
|
||||
|
||||
Copy that directory somewhere off the machine. A backup on the same disk as the database is not a
|
||||
backup.
|
||||
|
||||
Separately, an administrator can download the whole facility as one JSON file from Settings ›
|
||||
Data at any time, and restore it into a fresh instance from the same screen. That file is the
|
||||
portable form; the `pg_dump` is the fast one.
|
||||
|
||||
### Restoring
|
||||
|
||||
```sh
|
||||
docker compose down
|
||||
docker volume rm threadcount_db threadcount_photos # only if you mean to replace everything
|
||||
docker compose up -d db
|
||||
docker compose exec -T db pg_restore -U threadcount -d threadcount --clean --if-exists < 2026-09-13-033001/threadcount.dump
|
||||
docker compose up -d
|
||||
docker compose cp 2026-09-13-033001/photos app:/data/ # after extracting photos.tgz
|
||||
```
|
||||
|
||||
## The Android apps
|
||||
|
||||
The two Play apps (ThreadCount, the counter; ThreadCount Staff) load their screens from
|
||||
`https://threadcount.tech` and cannot be pointed at another host. On a self-hosted instance use
|
||||
the same screens in a phone's browser: `https://your-host/m` for the counter (camera scanning
|
||||
works in Chrome and Edge) and `https://your-host/my` for staff. Add either to the home screen.
|
||||
|
||||
## Environment reference
|
||||
|
||||
`.env.example` lists every variable the application reads, with a comment on each. The ones that
|
||||
matter for a Community instance are `SESSION_SECRET`, `POSTGRES_PASSWORD`, `NEXT_PUBLIC_SITE_URL`,
|
||||
`EDITION`, the `SMTP_*` group, and `SIGNUPS_DISABLED`. Leave the operations console, Cloudflare
|
||||
Access, Listmonk, Umami and GlitchTip variables empty unless you run those yourself.
|
||||
|
||||
## Getting help
|
||||
|
||||
Open an issue on the repository. If you would rather someone else ran it, that is what
|
||||
[threadcount.tech/pricing](https://threadcount.tech/pricing) is for.
|
||||
Reference in New Issue
Block a user