# GDPR compliance for a one-person email service precis.fyi is an AI email assistant — send a message to hey@precis.fyi, get a reply generated by Claude. It runs on a single Fly.io machine with a SQLite database. No company, no team, just a personal project that handles other people's email. That last part is what makes GDPR compliance non-optional. This is a writeup of the work done to bring the service into compliance, the reasoning behind each decision, and the references that grounded them. ## Starting position The service already had some privacy-relevant features before this work began: memory (per-user context stored across conversations), a data export tool, and a feedback system. But none of it was designed with GDPR in mind. Memory was enabled by default. There was no consent tracking. No erasure tool. No breach notification mechanism. No privacy policy. The disclaimer shown to first-time users didn't mention data processors or user rights. ## What GDPR actually requires Eight questions were researched against official sources (GDPR text, ICO guidance, EDPB guidelines) rather than relying on training-data assumptions. The key findings: **Consent must be opt-in.** EDPB Guidelines 05/2020 on consent under Regulation 2016/679 are clear: valid consent requires an affirmative act. Pre-ticked boxes or features enabled by default do not constitute consent. Memory and web tools both involve processing personal data for purposes beyond the core service, so both need explicit opt-in. **All processors must be named.** Articles 13 and 14 require transparency about who processes user data. The service uses three external processors: Anthropic (AI via Claude API), Postmark (email delivery), and Fly.io (infrastructure/hosting). All three must appear in any privacy disclosure — including the first-time notice. **Purpose-specific consent, not tool-specific.** EDPB guidance requires consent per *purpose*, not per tool. Web search and web fetch both serve the same purpose (retrieving external information to answer questions), so a single "web tools" toggle is compliant. Splitting them would add friction without improving informed consent. **Erasure has exceptions.** Article 17(3)(b) allows retention of data necessary for compliance with a legal obligation. Breach notification contacts (required under Article 34 to notify users of data breaches) can be retained even after an erasure request. The implementation retains only email address and an expiration date, rounded to end-of-month ~3 years out to avoid leaking precise interaction dates. **Workflow records and "beyond use."** The service processes emails through DBOS workflow steps that auto-purge after 7 days. ICO backup guidance considers data "beyond use" when it's in a system where deletion is applied on a defined schedule. No additional erasure mechanism is needed for these records. **Rectification means user wishes take precedence.** Article 16 gives users the right to correct inaccurate data. For memories, this means the user's stated correction overrides whatever the AI previously stored. The implementation accepts the user's instruction and rewrites the full memory set accordingly. ## What was built ### Consent model Memory and web tools both changed from opt-out to opt-in (disabled by default). Consent timestamps are recorded (`memory_consent_at`, `web_tools_consent_at`) to satisfy Article 7(1)'s requirement that the controller be able to demonstrate consent was given. ### User rights tools Five tools map to specific GDPR articles: | Tool | Right | Article | |---|---|---| | `recall_memories` | Access | Art. 15 | | `manage_memories` | Rectification | Art. 16 | | `delete_all_data` | Erasure | Art. 17 | | `request_data_export` | Access/Portability | Art. 15, 20 | | `update_setting` | Consent withdrawal | — | `delete_all_data` wipes memories, settings, and feedback but explicitly does *not* delete breach notification contacts (Art. 17(3)(b) exemption). ### Breach notification contacts A new `breach_notification_contacts` table stores email and expiration date only. Updated on every inbound email. Expired contacts are pruned daily by the scheduled garbage collector. The expiration is set to end-of-month ~3 years from last interaction — rounding avoids leaking precise usage timestamps. ### Privacy policy Written based on Basecamp's open-source privacy policies (github.com/basecamp/policies, CC-BY-4.0). Published at `/privacy` as rendered HTML. The full text is also loaded into the AI's knowledge tool so it can answer questions about the policy accurately. ### Disclaimers First-time users see a notice covering: AI-generated content, data processors, opt-in features, erasure rights, and a link to the privacy policy. The version is tracked per-user; bumping a constant re-shows updated disclaimers to everyone without touching the database. ### SQLite upsert fix The existing `_set_memory_enabled()` function used `INSERT OR REPLACE`, which silently drops columns not mentioned in the INSERT. When `disclaimers_version_seen` and consent timestamps were added, this would have clobbered them. All upserts now use `INSERT ... ON CONFLICT DO UPDATE SET col = excluded.col`. ## What's still manual Data Processing Agreements (DPAs) with the three processors need to be verified or requested: - Anthropic: covered under their Commercial Terms of Service - Postmark: DPA available at postmarkapp.com/eu-privacy - Fly.io: request from compliance@fly.io These are operator tasks, not code changes. ## References - GDPR full text: [eur-lex.europa.eu/eli/reg/2016/679/oj](https://eur-lex.europa.eu/eli/reg/2016/679/oj) - EDPB Guidelines 05/2020 on consent: [edpb.europa.eu/sites/default/files/files/file1/edpb_guidelines_202005_consent_en.pdf](https://edpb.europa.eu/sites/default/files/files/file1/edpb_guidelines_202005_consent_en.pdf) - ICO guidance on the right to erasure (backups): [ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/individual-rights/right-to-erasure/](https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/individual-rights/right-to-erasure/) - Basecamp open-source policies: [github.com/basecamp/policies](https://github.com/basecamp/policies) (CC-BY-4.0)