If customer feedback lives in a repository, email addresses can end up in it whether you intend that or not: a form field, a pasted bug report with a signature, or a commit's own author metadata. The fix is not to avoid repositories for this kind of content. It is to keep raw addresses out of the version-controlled text in the first place, and to know what to do on the day one gets in anyway.
We are not lawyers, and nothing here is legal advice. This describes the technical mechanics of how an address gets into a repository, what to store instead, and what happens once it is there. What your own obligations are around that data is a question for you and your own adviser.
This page is one part of a larger guide. For the case for running a support centre this way, see self-hosting a feature request board.
Why would a customer's email end up in a repository at all?
Three ordinary routes, none of them malicious. A submission form asks for an address so you can reply, and if that form's output is written straight into a content file, the address goes with it. A pasted bug report carries an email signature or a forwarded thread with someone's address still in it, because the person pasting it was not thinking about where the paste ends up. And if any part of your workflow commits on a submitter's behalf, using their address as the commit author rather than a service account, that address is now sitting in the repository's permanent commit log, not just in a file you could edit later.
None of these require carelessness. They are what happens by default when a form's raw output becomes a file's raw content, which is exactly the shape of a Markdown-based support centre.
What should you store instead of the raw address?
For most of what a support workflow needs, you do not need the plaintext address sitting in a version-controlled file at all. You need two things: a way to recognise the same person again, and a way to reach them. Neither requires the address to live in readable text inside the content.
A hash or an opaque identifier does the first job. If you need to know whether the person who filed this bug also voted on that feature request, a consistent hash of their address does that without the address ever appearing in plain text anywhere a collaborator, a fork or a CI log might read it. Reaching the person is a separate concern from recognising them, and it belongs in a system built to hold contact details securely, not in a content file whose whole point is to be readable by anyone with repository access.
Where should the actual address live, if not in the repository?
In a secrets store or a separate private system, not the content repository, and ideally encrypted rather than stored as plain text even there. Docket's own handling is a working example of the split: submitter email addresses are encrypted at rest, with different ciphertext produced each time even for the same address, kept entirely apart from the public Markdown content that makes up the post. The visible feedback, the thing collaborators and readers are meant to see, never carries the address at all.
That separation is the whole point. A content repository and a place for contact details are two different jobs, and conflating them means every person with read access to one now has read access to both, whether that was the intention or not.
Does making the repository private actually fix this?
Partly, and it is worth being precise about which part.
| What private controls | What it does | What it does not do |
|---|---|---|
| Current collaborators | Limits who can view the repository today to people you added | Nothing about anyone added later, who inherits full history on day one |
| New forks | Prevents a stranger from forking a repository they cannot see | Does not un-fork a copy made while public, or made by anyone with legitimate access |
| Search engines | Keeps the content out of public indexes | Does not affect CI logs or exported archives that already captured the content |
| Commit history itself | Nothing. Privacy is a visibility setting, not a change to what history contains | Every commit ever made, including from before the repository went private, is still there |
Private is a real and worthwhile control. It is not a substitute for keeping the address out of the content in the first place, because the moment visibility changes again, everything ever committed is exactly as visible as it always was.
One payment, no subscription, unlimited products.
How do you stop a pasted address from being committed in the first place?
Treat submitted content as untrusted text that needs a pass before it becomes a file, not as text that goes straight from a form field to a commit. A pattern match for anything that looks like an email address, run against pasted content before it is written, catches the common case: a signature block, a forwarded thread, a "reply to me at" line someone typed without thinking. It will not catch every address in every format, and should not be the only control, but it removes most of the routine exposure.
Separately, check what your automation uses as the commit author. If any part of the workflow commits using a submitter's own address rather than a fixed service account, that address is embedded in the commit log itself, and the scrub above never sees it, because it never touched the file content. Check the author field on its own, precisely because it is easy to miss.
What do you do when an address gets committed anyway?
It will happen eventually, however careful the scrub is, and the honest first fact is that deleting the file or amending the latest commit does not remove the address from history. Git keeps every version of everything by design, the same property that makes it useful for tracking changes and makes an accidental commit hard to undo: even rewriting the history leaves every existing clone untouched. Git history is permanent covers what that actually means and what your real options are, because the answer is more involved than one command.
Whose responsibility is this, on a tool like Docket?
Docket stores every post as a Markdown file in your own repository, which means you control where that repository lives and who can see it. That is a genuine advantage: nobody else's infrastructure, nobody else's access list, nobody else's breach to wait on a notification about. It is also a genuine responsibility: keeping raw addresses out of that content, checking commit authorship, and deciding who gets added as a collaborator is entirely yours to run, not a vendor's.
Frequently asked questions
Why would a customer's email address end up in a git repository?
Usually through a form field written straight into content, a pasted bug report carrying a signature or forwarded thread, or automation that commits using the submitter's own address as the author.
What should I store instead of the actual address?
A hash or an opaque identifier for recognising the same person again, with the real address held separately in a secrets store or a dedicated private system, ideally encrypted. Docket encrypts submitter addresses at rest, with different ciphertext each time, kept apart from the public post content.
Does making the repository private fix the problem?
Only partly. It limits who can see the repository from today onward, but does nothing to the commit history that already exists, does not affect anyone added as a collaborator later, and does not undo any fork or CI log capture made before the repository went private.
How do I stop a pasted address from being committed in the first place?
Run submitted content through a pattern match for anything that looks like an email address before it is written to a file, and separately check what your automation uses as the commit author, since an address used there never passes through a content scrub at all.
What do I do if an address already got committed?
Deleting the file or amending the latest commit does not remove it from history. Git history is permanent covers what actually needs to happen once something has been committed.