Skip to main content

LEGAL

Privacy Policy

How Troy Kelly's personal site collects, uses, shares and stores information — no accounts, no tracking cookies, cookieless analytics.

IN EFFECT

This is my personal website. I built it to be low-collection by design: you can read almost everything here without telling me who you are, and I do not run advertising, tracking cookies, or profiling of any kind. This policy explains, in plain language, exactly what information the site touches, why, who helps me run it, where that information goes, how long it is kept, and how to reach me about any of it.

Who I am and how to contact me

I am Troy Kelly, an individual based in Sydney, New South Wales, Australia. I run this site personally. There is no company behind it — no business entity, no ABN or ACN, and no registered office. When this policy says “I”, “me”, or “my”, it means me, Troy Kelly, personally.

The only way to contact me about this site or your privacy is by email:

There are no contact forms, comment forms, or live-chat tools on the site, so email is the sole channel. Please use that address for any privacy question, correction, or request to be removed from my newsletter.

Note that the automated email the site sends (the newsletter confirmation, described below) is delivered from hello@troykelly.com. That is a sending address only; troy@troykelly.com is the address to write to if you want a person to read and act on your message.

My approach to your privacy

I keep the reader experience deliberately private:

  • No accounts and no login. There is no reader sign-up, profile, or password. You can use the entire public site anonymously, without identifying yourself.
  • No tracking or advertising cookies. The public site sets no advertising cookies and no cross-site tracking cookies.
  • No cross-site tracking and no profiling. I do not build a profile of you, track you across other websites, or target advertising.
  • Cookieless analytics. My traffic measurement does not use cookies and does not create a persistent visitor identifier.
  • No sale of data. I do not sell, rent, or trade personal information to anyone, and I never will as long as I run the site this way. If that ever changed, this policy would change first.

The rest of this document explains the small amount of information the site does handle, so that the summary above is verifiable rather than just a promise.

What I collect, why, and where it is stored

The public site collects almost nothing unless you choose to use one of a few features. Here is each surface, one by one.

Newsletter subscription

If you subscribe to my newsletter, I store your email address (trimmed of surrounding spaces, lower-cased, and limited to 254 characters), your subscription status and the related timestamps (when the record was created, updated, sent, confirmed, and, if applicable, unsubscribed), and a SHA-256 hash of the single-use confirmation token — never the raw token itself.

This is stored in a Cloudflare D1 database. I collect it only through double opt-in: your address receives nothing until you click the confirmation link in the email I send you. The purpose is simple — to send you the newsletter with your verified consent, and nothing else. See the “Direct marketing” section below and my Newsletter & Anti-Spam Terms for the full detail.

IP address (used only during the request)

The site reads your IP address (from Cloudflare’s CF-Connecting-IP) and uses it only transiently, in two ways:

  • as an abuse rate-limit key on the newsletter, ask-the-blog, and search endpoints, so that automated abuse can be throttled; and
  • on production pages, forwarded to my analytics instance during the request, so that Plausible can derive coarse country/region information and its own daily hash (see below).

The site itself keeps no IP log. Your IP is used in the moment and not stored as a visitor record by the application.

Analytics data

I measure traffic with a self-hosted, cookieless analytics tool (Plausible). It derives aggregate pageviews and events, the page URL, your browser’s User-Agent, and coarse geography inferred from your IP. There is no cookie and no persistent identifier tied to you. Analytics runs only on the live production site. Full detail is on my Cookies & Analytics page.

Search queries

If you use the search box at /writing/search, the text you type is recorded against the analytics Search event (trimmed to 120 characters), together with a count of how many results it returned. I use this only to understand what readers are looking for so I can write about it. Please keep in mind that whatever you type into search reaches my analytics store as event text, so avoid typing anything personal or sensitive into it.

Ask-the-blog questions

If you use the “ask the blog” feature, the question you submit (up to 500 characters) is processed during the request to generate an answer drawn from my published posts. It is not stored or logged by this site — the endpoint neither writes it to a database nor logs it. To answer you, your question is sent to the AI gateway and model described in the “Who I share it with” section below; I cannot control or promise what that downstream provider does with it once it leaves the site, so I only tell you what the site itself does: it does not retain it. The answers are machine-generated and may be inaccurate, incomplete, or fabricated — see my Disclaimer & No Professional Advice and verify anything important against the cited source articles.

Server and edge request logs

Like any website, this site runs on hosting infrastructure. Cloudflare processes each request’s IP address, User-Agent, requested URL, and connection metadata in order to serve the site to you. Those edge logs are Cloudflare’s own logs, governed by Cloudflare, not created or retained by my application code.

Cookies and on-device storage

The site has a near-zero cookie footprint. There are no advertising, analytics, or tracking cookies on public reader pages, and the site does not set tracking cookies as you browse.

The site does keep a few small records in your browser’s localStorage and sessionStorage. They exist only to remember how you want the pictures and motion on the /story Show and on picture-ground pages (the home page and the 404 page) to display, and to avoid repeatedly loading pictures that failed to run well on your device. They stay in your browser and are never transmitted to me or to anyone else. Two of them, tk-show:strikes and tk-show:hard, hold your browser’s User-Agent string and timestamps. The records are:

localStorage (the browser has no expiry for it; it stays until overwritten, removed as described, or you clear it):

  • tk-show:choice holds script or show, the choice you made with the Pictures / Text only switch (on /story, or in the footer of the home and 404 pages). It is used to show you the version you chose, except that choosing Pictures cannot start them on a browser that cannot run them at all. It stays until you change it or clear your site data.
  • tk-show:strikes holds your browser’s User-Agent string and a short list (at most six) of recent tab sessions in which the /story pictures stepped down, each with a random session id, a timestamp and the level the page stepped down to (lighter pictures or text only). The site writes it each time the pictures step down, where the browser allows sessionStorage. It is used to start /story on text only when three of those sessions inside 30 days stepped down to text only, and on lighter pictures when three sessions of any kind did. The 30 days is only the period in which an entry counts towards that decision. It is not a deletion date. An entry older than 30 days is ignored, and the whole list is ignored if your User-Agent has changed. Such entries are dropped only when the record is next written. The site removes the whole record in two cases only. First, when you press Pictures on the /story switch while it shows Text only. Second, when, during one load of /story, the pictures run and pass six frame-rate checks that do not call for a step down, with no step down earlier in that load. That second case applies however the pictures came to run, including when you chose Pictures. The pictures do not run on /story when you have chosen Text only, or when your browser cannot run them at all, in which case the switch is not shown. Otherwise they run unless something holds the page on text: a reduced-motion, data-saver or reduced-data setting, a 2G connection, under 4 GB of memory, under four processor cores, graphics the site rates too low, this tab having already stepped down to text only, or, if this tab has not stepped down at all, three text-only entries in this record inside 30 days. Choosing Pictures overrides those holds. Nothing else removes the record. Choosing Text only, the footer switch on the home and 404 pages, and the passing of 30 days do not remove it. It stays until a later step down rewrites it or you clear your site data.
  • tk-show:hard holds your browser’s User-Agent string and a timestamp. Each time /story runs its check, the site writes it with a fresh timestamp if it finds that the pictures cannot run on your browser at all, and removes it if the check ends any other way, including when the page stays on text for another reason. It is read before /story first paints, to set the text up for a browser that cannot run the pictures. The site ignores it once it is more than 30 days old or if your User-Agent has changed, but it does not delete it then. It stays until the next /story check or until you clear your site data.
  • tk-show:paused holds 1 when you pause the motion on /story, and is removed when you resume it. It keeps the motion paused on later visits.

sessionStorage (kept for the browser tab session; the browser discards it when the tab is closed):

  • tk-show:session holds the level the page stepped down to in this tab and the time, written each time the pictures step down. It is used to keep that level for the rest of the tab session. It is also removed when you press Pictures on the /story switch while it shows Text only.
  • tk-show:sid holds a random id for this tab session, written the first time the pictures step down in that tab, so one session counts as at most one entry in tk-show:strikes.

You can remove all of these at any time by clearing your browser’s site data for troykelly.com. None of them is transmitted to any server.

For the complete list and the reasons there is no cookie banner, see my Cookies & Analytics page.

How I use your information

I use the small amount of information above only for the purposes I have already described, consistent with Australian Privacy Principles 3 and 6:

  • your newsletter email, to send you the newsletter you asked for;
  • your IP, transiently, to rate-limit abuse and to let analytics derive coarse geography;
  • analytics and search data, in aggregate, to understand how the site is used and what readers want; and
  • your ask-the-blog question, during the request, to generate an answer.

I do not use this information for any secondary purpose. There is no sale, no rental, no profiling, and no advertising. If I ever wanted to use information for a new purpose, I would seek your consent or update this policy first.

Who I share it with (service providers)

I rely on a small set of service providers to run the site. Each receives only what it needs for its task:

  • Cloudflare, Inc. — hosting, content delivery, edge compute (Workers), and the data stores behind the site (D1, KV, Vectorize, and the rate-limiter). Cloudflare processes every request’s IP, User-Agent, URL, and connection metadata, and stores newsletter subscriber records (D1) and post embeddings (Vectorize).
  • Postmark — transactional email delivery. It sends the newsletter double opt-in confirmation (from hello@troykelly.com). It receives the recipient email address, the confirmation link, and the message subject and body.
  • Self-hosted Plausible analytics (operated by Aperim), at pulse.aperim.com — cookieless web analytics, proxied same-origin through the site. It receives the visitor IP (transiently, to derive coarse geography and a daily hash that, by Plausible’s design, is not retained as a cross-day identifier), the User-Agent, the page URL, and, for searches, the search-query text as an event property.
  • The Aperim LLAP AI gateway, at llap.public-servers.sy3.aperim.net, and its underlying model providers — AI inference for ask-the-blog and semantic search. Generation uses a GPT-5.x model (gpt-5.4-mini); embeddings use Voyage AI (voyage-4). These receive your ask-the-blog question (and, on the semantic search path, your search-query text), together with excerpts of already-published posts as context.
  • Embedded-media providers (YouTube, Vimeo, SoundCloud) — only on posts that embed their media. When such a post loads, your browser contacts the provider directly, and the provider receives your IP and User-Agent and may set its own cookies inside its iframe. A typical article page makes no third-party requests at all.

Overseas disclosure

Some of the processing above occurs outside Australia (Australian Privacy Principle 8):

  • Cloudflare is a United States company operating a global edge network, so processing may occur outside Australia for every request.
  • Postmark is United States-based; it receives your newsletter email address.
  • The AI model providers behind the LLAP gateway — the GPT-5.x generative model and Voyage AI embeddings — are United States-based; they receive ask-the-blog question text and semantic search-query text.
  • The embedded-media providers (YouTube/Google, Vimeo, SoundCloud) are United States-based, when a post embeds their media.

Two of the endpoints I use are operated by Aperim: the analytics host (pulse.aperim.com) and the AI gateway (llap.public-servers.sy3.aperim.net). Some processing there occurs overseas, and I am still confirming those hosts’ physical locations, so I do not state a specific country or region for them here. I will update this section once those locations are confirmed.

By using the newsletter, search, or ask-the-blog features you accept that the information involved is handled by the overseas providers described above.

How long I keep it

I keep information only as long as I need it:

  • Newsletter subscriber records (email, status, timestamps) are kept until you unsubscribe. If you never confirm, the unconfirmed record is deleted automatically 30 days after the confirmation link expires. Once confirmed, a record stays until I remove it (see “Accessing, correcting, or deleting your information” below), which I do when you ask.
  • The administrative audit log records who did what in the admin area, including the email address of a sign-in attempt by an allow-listed person. Ordinary entries are deleted after 400 days. Entries recording reviewer access, thesis document uploads and reuse (including the uploader’s email address), and changes to an account’s access, role or sessions (invitations, role changes, suspension, reactivation and sign-out of all devices) are kept for 7 years.
  • Editorial history and research notes (earlier versions of articles, and the working notes behind them) are pruned after a year, except versions still in use and the newest 50 versions of each article, which I keep whatever their age.
  • The confirmation token hash expires after 7 days and is cleared as soon as you confirm your subscription.
  • Analytics keeps no per-visitor record on my side. Your IP is forwarded to Plausible during the request and is not logged by the application.
  • Ask-the-blog questions and search text are not persisted by the site. (Search text does survive as an aggregate analytics event property, as described in the analytics section of my Cookies & Analytics page.)
  • Cloudflare’s edge logs are governed by Cloudflare, not configured or retained by my application code.
  • The display-preference records described above stay in your own browser. The 30-day period only stops an old record from affecting the site; it does not delete it. The sessionStorage records go when the tab session ends, and tk-show:session also goes when you press Pictures on the /story switch while it shows Text only. tk-show:choice changes only when you use a switch. tk-show:hard is rewritten or removed each time /story runs its check. tk-show:paused is removed when you resume the motion. For tk-show:strikes: The site removes the whole record in two cases only. First, when you press Pictures on the /story switch while it shows Text only. Second, when, during one load of /story, the pictures run and pass six frame-rate checks that do not call for a step down, with no step down earlier in that load. That second case applies however the pictures came to run, including when you chose Pictures. The pictures do not run on /story when you have chosen Text only, or when your browser cannot run them at all, in which case the switch is not shown. Otherwise they run unless something holds the page on text: a reduced-motion, data-saver or reduced-data setting, a 2G connection, under 4 GB of memory, under four processor cores, graphics the site rates too low, this tab having already stepped down to text only, or, if this tab has not stepped down at all, three text-only entries in this record inside 30 days. Choosing Pictures overrides those holds. Nothing else removes the record. Choosing Text only, the footer switch on the home and 404 pages, and the passing of 30 days do not remove it. It stays until a later step down rewrites it or you clear your site data. Otherwise the records remain until overwritten or until you clear your site data.

How I keep it secure

I take reasonable steps to protect the information the site handles (Australian Privacy Principle 11):

  • the site is served over HTTPS, and insecure requests are upgraded;
  • a strict Content-Security-Policy limits what can run and load;
  • confirmation tokens are stored only as SHA-256 hashes, never in the clear, and are cleared once used;
  • secrets are held in 1Password, referenced by name, and never committed to code or logged; and
  • fonts are self-hosted, so no third-party font network is contacted as you browse.

If a data breach likely to cause serious harm ever occurred, I would notify affected subscribers and, where appropriate, the OAIC, consistent with the Notifiable Data Breaches scheme.

Accessing, correcting, or deleting your information

You can ask me about the information I hold about you, ask me to correct it, or ask me to delete it, consistent with Australian Privacy Principles 12 and 13. Email me at troy@troykelly.com.

In practice, the only personal information I store is your newsletter subscription. To come off the list or have your record erased, email that address and I will remove the row from the database by hand. There is no self-service unsubscribe button yet, so a request to me by email is the way it is done today, and I action it promptly. The subscription database has no built-in way to edit a stored address, so if your address changes, the honest fix is to unsubscribe the old one and subscribe the new one (or email me and I will help).

Direct marketing

The only marketing I do is my email newsletter, and you receive it only after confirming your subscription through double opt-in — nothing is sent to an address that has not been confirmed. You can unsubscribe at any time by emailing me at troy@troykelly.com, and I will remove you.

My newsletter obligations are governed by the Spam Act 2003 (Cth), which applies regardless of my size. As a matter of good practice I also follow the spirit of Australian Privacy Principle 7 on direct marketing, even where it may not strictly bind me. The full detail — consent, sender identification, and unsubscribing — is in my Newsletter & Anti-Spam Terms.

Children’s privacy

This site is a personal blog and portfolio intended for a general adult audience. It is not directed at children, and I do not knowingly collect personal information from children. If you believe a child has provided me with personal information (for example, by subscribing to the newsletter), email me at troy@troykelly.com and I will delete it.

Complaints

If you have a concern about how I have handled your information, please contact me first at troy@troykelly.com. I will acknowledge your complaint promptly and aim to respond within 30 days.

If you are not satisfied, you may be able to raise the matter with the Office of the Australian Information Commissioner (OAIC) at oaic.gov.au. Please note that, because I am an individual and not a business, a small-business or individual exemption under the Privacy Act may apply and the OAIC may decline jurisdiction. I nonetheless maintain and follow this policy as a matter of good practice, whether or not the Privacy Act formally binds me.

This site is operated from Australia, and I treat New South Wales, Australia as the governing jurisdiction for privacy matters relating to it.

International readers

The site is served worldwide, and anyone, anywhere, can read it, subscribe to the newsletter, or use ask-the-blog. It is nonetheless an Australian site: operated from Australia, intended primarily for an Australian audience, and handled under Australian law. If you use these features from outside Australia, you consent to your information being processed in Australia and transferred to and processed by the overseas providers described in the “Who I share it with” and “Overseas disclosure” sections above. I do not offer a separate regime for other countries.

Changes to this policy

I may update this policy from time to time as the site evolves. The effective date shown at the top reflects the current version, and any material change will be noted there. Please check back if you want to know how I currently handle information.