How this is built

Written for the officer who has to answer for it in front of a committee. Every claim here is one you can check on a screen or in the source.

Names and votes are never in the same place

This is the whole design. Your register and your ballot box are two separate stores, and there is no column in either that could join them — not a permission that could be granted, not a setting that could be changed. The join does not exist to be made.

Why it is built this way rather than locked down

A permission can be granted by a determined administrator, a court order or a mistake. A column that was never created cannot be. If somebody stole the entire database tonight, they would hold your members’ names and a pile of unattributable ballots, and no amount of work would pair them up.

What happens when a member votes

  1. They open their link, or type their code. We check it against a keyed hash — the code itself is not stored, so there is nothing to look up or leak.
  2. The register records that this member’s code has been used. It records nothing about what they chose.
  3. The ballot goes into the ballot box under a fresh random reference, with the time rounded to the hour so that the order of voting cannot be matched against the order of code use.
  4. The two writes happen together, and nothing anywhere connects them. The member is shown their reference once, and no copy is kept.

What is encrypted, and how

Named algorithms rather than the word "encrypted", because "encrypted" on its own tells you nothing.

WhatHow
Everything in transit HTTPS (TLS) on every page and every form, with HSTS. The connection to the database runs over the host’s private network and is encrypted in transit.
Voting codes Never stored. We keep HMAC-SHA-256 of each code under a secret pepper that is held in the application’s environment, not in the database — so a stolen database yields no usable codes. Each code is two independent random secrets: 128 bits behind the link, 80 bits behind the typed code.
Officer passwords scrypt, with N=32768, r=8, p=1 and a per-password salt. Plus a six-digit code from your authenticator app on every sign-in.
Stored service secrets AES-256-GCM with a key derived by HKDF from a deployment secret. This covers mail-provider tokens and any mail credentials your club supplies.
The record of what was done A SHA-256 hash chain: every event carries the hash of the one before it, so altering or removing any entry breaks every hash after it. The chain is verified on screen and its head is published with the result.

Data at rest sits on the host’s encrypted managed Postgres, and any backup of it stays in the same region.

Where it runs

The application and the database both run in Frankfurt, in the European Union. There is no other region, no replica elsewhere, and no analytics or advertising service anywhere on any page — so no request from a voter’s browser leaves the EU.

If your club sends its codes by email through this service, the messages are handed to a mail provider for delivery; if you would rather that did not happen, choose printed letters or your own mail server when you set the election up.

Two officers, or it does not happen

The controls that matter are enforced by the database, not by a screen. One officer requests; a different one agrees. The database refuses if they are the same person, and every request and approval is written into the hash chain with the reason given.

Launching an election, closing it before its time, publishing the result, replacing a member’s lost code, producing a list of live codes, and purging records — each needs two.

What we do not collect

Not "we do not use" — not collected at all, and in most cases structurally impossible to collect.

  • No IP address or browser fingerprint is stored with a ballot. Rate limiting uses a short-lived keyed hash that is never associated with one.
  • No open tracking or click tracking in any email. A click tracker would route your members’ voting codes through a third party’s redirect log.
  • No analytics, no advertising, no session replay, no third-party scripts or fonts. The page’s own security policy forbids loading anything from anywhere else, so this cannot be added by accident.
  • No cookies on the voter path beyond a token that protects the form and, if the visitor switches language, the language they chose. Neither identifies anybody.
  • No plaintext voting codes after dispatch — not in the database, not in logs, not in exports. There is a test that asserts it.

Data protection

Your club is the data controller: it decides who is on the register and why, and it holds the relationship with your members. This service is a processor acting on your instructions, and processes personal data only to run the elections you set up. Your privacy notice needs to name your club as controller and to say where the data is held.

Your members’ rights

  • Access, correction and erasure apply to the register — a member can ask your officers what is held about them and have it corrected or removed.
  • They cannot apply to a ballot that has been cast, because no ballot is connected to a person. This is worth saying in your privacy notice: it is a feature, and it heads off a request that could never be fulfilled.
  • A member can always check that their own ballot was counted, using the reference they were shown, without revealing to anybody how they voted.

How long things are kept

Nothing is deleted automatically. Your club decides its retention schedule and carries it out, which is the right way round — we should not be quietly destroying your election records on a timer. Here is what exists to be kept or deleted.

WhatHow long
Plaintext voting codesNever stored at all. They exist only inside the message or letter being produced.
Print packs and mail-merge filesWritten to disk when you ask for them and yours to delete once they have been sent. Each carries a note saying so.
The register: names and contact detailsKept until your club deletes it. Delete it once your challenge period has passed; your constitution or your privacy notice should set the date.
Anonymous ballots and the sealed resultKept as your club’s election record. They contain no personal data, so there is usually no reason to delete them.
The audit logKept as long as the election record. It names officers and what they did, not how anybody voted.

Purging an election’s records is itself a two-officer action, so no single person can quietly delete the evidence of an election.

What this does not protect you against

If a member gives their code to somebody else, that person can vote instead of them. A code is a secret in the post or the inbox, and that is the trade for not making members hold accounts.

If an officer’s laptop or email is compromised, an attacker sees your register. They still cannot see how anyone voted, and they cannot act alone: everything that matters needs a second officer.

This has not had an external security audit. What is published is the evidence a result rests on: every anonymous ballot, the file hashes, and the independent recount tool, with instructions — so you, or somebody you trust, can check any result for yourself even though you cannot read the source.

We can never tell you how a particular member voted, however the question is asked and whoever asks it. That is not a policy we could change our minds about.

Still have a question?

The common ones, for members and for committees, are answered on one page.

Check a result Common questions