How we run the service
Security Overview
DKT Digital, St Peter Port, Guernsey GY1 · info@dktdigital.co.uk
DRAFT — pending legal review, not binding. This document has not been reviewed by a solicitor. It is published so you can read our terms before you talk to us, and so we can be held to what it says — but it is a working draft, not a vetted contract, and nothing here is legal advice. A binding version will be issued for signature only once it has been through legal review. If you are relying on any part of it, ask us and we will tell you where it stands.
Where your data physically lives, what is encrypted and what is not, every outside company that can process it, and how backups and access control work. Measured against the running system rather than described from intention.
*Last updated: 2026-08-02 (§6: the former-client purge is now switched on at 90 days. Hosting, encryption and backup sections re-measured against the live system after the VPS migration on 2026-08-01). Owner: Daniel Thomas, DKT Digital (Guernsey).*
1. Where your data is hosted
Measured 2026-08-01, not assumed. The automation engine and the database run on a dedicated virtual server operated by Hetzner Online GmbH in Falkenstein, Saxony, Germany (EU). The public website, client portal, and edge security are served through Cloudflare. Nothing client-facing runs on the owner's own hardware any more; the earlier "Guernsey hardware" statement in this document described the pre-migration setup and was replaced once the move was verified.
So, in one sentence: **your data lives in Germany (EU), reached only through Cloudflare, and DKT Digital — the business you contract with — is established in Guernsey.**
What that means for the law that applies:
- DKT Digital is established in Guernsey, whose data-protection law (the Data Protection (Bailiwick of Guernsey) Law, 2017) is GDPR-aligned, with its own regulator, the ODPA.
- The data is processed in Germany, inside the EU/EEA. Guernsey is the subject of a European Commission adequacy decision, and the EU/EEA is not a "third country" for most purposes here — but the transfer analysis for this routing is a question for the solicitor to confirm, not one this document settles. What this document is responsible for is stating the route accurately.
- Transfers outside the UK/EEA — the model providers and the US-based tools in §3 — are the ones that need an explicit transfer mechanism; see §3 and DPA §7B.
2. Encryption
In transit: all traffic is over TLS/HTTPS. The website, client portal, form submissions, and every API call to a third party use encrypted connections. Public endpoints sit behind Cloudflare with modern TLS.
At rest:
- Verified 2026-08-01 on the live server: the database volume is a plain
ext4filesystem with no block-level encryption (/dev/mapperholds onlycontrol, i.e. no LUKS device). So the operational client database is not encrypted at rest at the disk level — this is a known, stated gap, not a claim we make and then qualify. Access to it is controlled instead (the database is not internet-facing, administrative access is not publicly exposed, and the host is single-tenant). Closing it properly means an encrypted volume on the host; until that exists, this document says so. - Per-client accounting/banking OAuth tokens are additionally encrypted at the row level (
pgp_sym_encrypt) with a key held only in the server environment, never in the database or in version control. Verified 2026-08-01 by reading the stored bytes directly: they begin with the OpenPGP packet header, i.e. they are genuinely ciphertext. So even a database dump does not expose a client's Xero/bank tokens in readable form. - Backups: the off-site copy is encrypted, the on-box copy is not. The nightly bundle written to the server itself is a
tar.gz, root-readable only, not separately encrypted. The copy replicated off-site to Cloudflare R2 every night is encrypted withagebefore it leaves the machine (.tar.gz.age), under a key that is not stored in the bucket. Per-client tokens stay pgp-encrypted inside both. See §5. - Application secrets (API keys, webhook secrets, the portal shared secret) live only in a permissions-restricted secrets file on the server and are injected as environment variables — never committed to the code repository. This is enforced by an automated secret-scan on every structure check and by the 10-minute watchdog.
3. Sub-processors — who else touches client data
We use third-party services to deliver the automations. Below is the **full list of sub-processors that can process client personal data**, enumerated from the live automations (not guessed). The client remains the data controller for their own customers' data; DKT Digital and these providers are processors/sub-processors.
| Sub-processor | What it does with client data | Primary region |
|---|---|---|
| Hetzner Online GmbH | Hosts the server the automation engine and the client database run on — every piece of client data at rest sits on this machine | Germany (Falkenstein) |
| Cloudflare | Website/portal hosting, CDN, tunnel, access control (Zero Trust), bot protection (Turnstile), encrypted off-site backup storage (R2) — all client-facing traffic passes through it | Global edge |
| Resend | Sends transactional client emails (welcome, reminders, reports, invoice chases) | US |
| Brevo | Email delivery/transactional (secondary email path) | EU (France) |
| Google (Workspace / Cloud, via service account) | Calendar events for bookings (client name/email), optional Sheets sync, Search Console | US / EU |
| GoCardless | Direct Debit mandates and recurring retainer collection (client billing details) | UK |
| Stripe | Card payments / one-off charges | US / Ireland (EU) |
| TrueLayer | Open-banking bank-feed verification for accounting audits (client bank data) | UK |
| Xero | The client's own accounting data, accessed under the client's per-client authorisation | NZ / global |
| Twilio | SMS reminders/notifications (client phone numbers). Not live — listed because it is wired and would become a sub-processor the moment SMS is switched on for a client, not because it is processing anything today. | US |
| Calendly | Meeting booking (client/prospect name, email) | US |
| Crisp | Website live-chat (visitor/lead messages, email) | EU (France) |
| Hunter.io | Email-address verification for cold/nurture outreach only (never transactional client sends) | EU / US |
| Apollo | Prospect/lead sourcing for outreach (marketing contacts, not existing-client data) | US |
| Telegram | Operational alerts to the owner — client identifiers (names/emails) can appear inside alert messages | Global (Telegram FZ-LLC) |
| AI inference — Groq, Cerebras, Google Gemini, OpenRouter | LLM processing where a feature needs it (e.g. drafting a reply, summarising). See §4 on what is and isn't sent. | US |
Not sub-processors of client personal data (listed for completeness — these handle market data, the agency's own content/research, or internal ops, and do not receive client customers' personal data): Alpaca, Nasdaq, Yahoo Finance, Finnhub, CNN, SEC/EDGAR, arXiv, Hacker News, Reddit, TechCrunch, VentureBeat, Wikipedia (market/trading & research sources); Beehiiv (the agency's own newsletter, with its own subscribers); Dev.to, LinkedIn (the agency's own content publishing — Buffer was removed on 2026-08-02: its legacy REST API no longer accepts our class of token and retires in Feb 2027, so the integration was dropped and the three workflows referencing it deleted); Tavily, Exa, Firecrawl (web research); Dub.co (link shortening); GitHub (private code/workflow backup — secrets are held in the environment, not in the backed-up files); Notion, Slack (internal operations).
The solicitor should decide which of these belong in the contractual sub-processor schedule of the DPA. The table above is the honest engineering enumeration to work from.
4. AI / model use — your data is never used to train models
- Client data is never used to train, fine-tune, or improve any AI model. The AI providers we call (Groq, Cerebras, Google Gemini, OpenRouter) are used through their APIs, where inputs are processed to return a result and are not used for training under their API terms. We rely on that and state it plainly.
- Data minimisation: we send the least data needed for a task. Where a feature can work on non-personal content, we do not send names, emails, phone numbers, or financial detail.
- No Chinese-jurisdiction AI for personal or sensitive data: as a hard internal rule, client names, emails, phone numbers, financial account details, health data, and legal documents are never sent to models subject to China's National Intelligence Law (e.g. Z.ai/GLM, Moonshot/Kimi). Those are used, if at all, only for non-personal code/content tasks. Personal-data AI processing stays on Western (GDPR-aligned) providers.
5. Backups and recovery
*Re-measured 2026-08-01 against the live backup log, because the previous version of this section described the pre-migration Mac setup and reported the off-site leg as broken. It is not broken any more, and saying so is as much a correction as reporting a failure would be.*
- On-box: a nightly bundle at 02:30 containing the database dump, the automation engine's own database and the restore instructions, written to the server (
tar.gz, root-readable only, not separately encrypted). Every step is integrity-checked — an empty dump, an unreadable dump index or a failed SQLite integrity check aborts the run rather than saving a corrupt file, and any non-zero exit sends the owner a Telegram alert. There is no silent backup failure. - Off-site (Cloudflare R2) — working, and encrypted. The same bundle is encrypted with
agebefore it leaves the machine and replicated nightly to Cloudflare R2 (.tar.gz.age, last 14 retained). Read directly from the bucket on 2026-09-01 (not from a log): it holds 14 encrypted objects, oldest 2026-08-22, newest 2026-09-01 02:30 UTC, with no missing night in that range. Retention keeps the last 14 objects, not the last 14 nights — three ad-hoc runs on 2026-08-23 occupy three of those slots, so the recoverable off-site history is currently 11 nights, not 14. Anything older has aged out and the bucket can no longer evidence it. The previous wording here claimed the copy was verified "on every night" under a verification date of 2026-08-01; the bucket cannot support a claim reaching back that far, because the objects that would have proved it no longer exist. The decryption key is held outside the bucket, so possession of the bucket is not possession of the data. - Off-box (USB): retired. It was a copy to an external drive attached to the owner's Mac, which was only current when the drive happened to be mounted; the encrypted R2 leg replaces it with something that runs whether anyone is present or not.
- Restore is tested — a nightly dump has been restored into a throwaway database and its table and client-record counts verified against the live source (see our internal incident runbook §7). An untested backup is not a backup.
- Recovery objectives (proposal — confirm): recovery point objective (max data loss) ~24 hours (nightly backup) tightening as backup frequency increases; recovery time objective measured in the fire-drill and recorded in our internal incident runbook.
6. Data retention (per table)
Current reality: while you are a client, the operational database keeps your records for the life of the engagement and nothing of yours is deleted automatically. One narrow exception now exists and is switched on: once a client has left, the customer data they brought us is purged 90 days later. That rule is set out in full below — it covers two tables, it does not touch billing or payment records, and it is reversible for 30 days. The automation engine's own execution logs are pruned automatically. **Re-measured on the live container 2026-08-02: 72 hours / max 2,000 runs** (EXECUTIONS_DATA_MAX_AGE=72, EXECUTIONS_DATA_PRUNE_MAX_COUNT=2000) — this document previously said 240 hours / 5,000 runs, which was a figure nobody had checked against the box since the migration. Shorter, not longer, so nothing was over-retained; it is corrected here because a security document that is wrong in the safe direction is still wrong.
Tables that hold client personal data today (with the honest current retention):
| Table | Holds | Current retention | Proposed retention (for solicitor) |
|---|---|---|---|
clients, saas_clients | Client profile, onboarding answers, data-source declarations | Life of engagement | Duration + 6 years (financial records) or as agreed |
client_integrations | Encrypted Xero/bank OAuth tokens | Until disconnected | Deleted on offboarding / disconnect |
leads, outbound_queue | Prospect/lead contacts, outreach queue | Indefinite | 24 months from last contact, then purge |
invoices, invoice_chases, payment_events | Billing/payment records | Indefinite | 6 years (tax) |
meetings | Bookings (name/email/time) | Indefinite | 24 months |
client_reports, client_roi, savings_log | Outcome/reporting data | Indefinite | Duration + 12 months |
client_tasks | Setup/data-migration tasks | Indefinite | Duration + 12 months |
feedback | Client feedback | Indefinite | Duration + 12 months |
email_delivery_log | Delivery events (recipient email, status) | Indefinite | 12 months |
sms_log | SMS attempts (phone, status) | Indefinite | 12 months |
The former-client purge is SWITCHED ON — 90 days, decided 2026-08-02. This paragraph said the opposite until that date, and the change is deliberate:
retention_days_after_offboardis now 90 andquarantine_grace_daysis 30. What that means precisely, because a retention promise is only worth the exactness of its scope:
- Who it applies to. Only a client who has left — status
cancelledorcancellingand a recorded cancellation date more than 90 days ago. A client with no recorded leaving date is never in scope, because an unknown date must not be read as "left long ago".- What it deletes. Exactly two tables:
leads(only rows belonging to a client — their ingested customer list) andcustomer_replies(replies from their customers). The set is a declared allow-list inretention_scope, so widening it is a visible, reviewable change and not an edit to a query.- What it does NOT touch, by construction. Billing and payment records —
invoices,invoice_chases,payment_events— are not in that allow-list and cannot be reached by the purge at all. They are kept for the statutory period; the exact figure is the open solicitor question immediately below, and this change does not pre-empt it. Opt-out records (customer_suppression) are refused by a database constraint rather than by convention, because deleting an opt-out would restart the messaging the person asked to stop.- It is reversible for 30 days. Nothing is deleted outright. The first pass copies the row verbatim into
purge_quarantineand removes it from the live table; a second pass hard-deletes only after the 30-day grace, and a restore puts the row back in the meantime.- What it deletes today: nothing. No client has left, so no row is eligible. The rule is armed for the future rather than acting now, and the monthly report states the eligible count.
The "Proposed retention" column above remains a proposal, and outside the two tables named here nothing in it is enforced by a job. No document in this set should be read as promising automatic deletion of anything else.
RULED 2026-09-02: 6 YEARS — AND THE START POINT WAS ALSO WRONG. The Privacy Policy said client data is kept 7 years from the end of the contract, citing Guernsey financial record-keeping. Both halves of that sentence were wrong. The States of Guernsey record-keeping requirements under the Income Tax Law name 6 years for companies and legal entities, and the period runs from the end of the year in which the return is submitted, not from the end of the contract. 7 is not a Guernsey number — it is the UK convention, and the Privacy Policy cited Guernsey for it, which the source does not support. Under data protection the direction of the error also matters: holding personal data a year beyond the statutory period is an extra year of exposure with no justification, so longer is not safer. The Privacy Policy, the table above and DPA §7.2 now all read 6 years.
[SOLICITOR] — WHAT IS STILL OPEN, AND IT IS NOT THE NUMBER. A sole trader does not sit cleanly inside either bracket of that source: 6 years applies to companies and legal entities, and a separate 2-year period applies to individuals. Which one binds a Guernsey sole trader serving UK clients is a legal judgement and is deliberately not resolved here. **Publishing 6 while that is open is defensible; publishing 7 with a citation the source does not support was not.** Tracked as
LEGAL-RETENTION-6-VS-7-YEARS.
7. Access control
- Production access is currently the owner only. There is no second account with production access, and no named continuity contact has been appointed yet — so there is also nobody whose access needs revoking. Arranging that contact, and the sealed-access instructions that go with it, is an open item tracked in Service Level Agreement §6; this section will name the arrangement once it exists rather than describe one that does not.
- The client portal is fail-closed: a client can only ever retrieve their own data, keyed on the identity verified by Cloudflare Access (not on any value the user supplies). A request without a verified identity returns nothing.
- Internal proxy endpoints require a shared secret held only in the server environment (moved out of the workflow code 2026-07-20), and reject anything without it.
- Inbound webhooks that carry money/events (payments, email delivery) verify a signature (HMAC / svix) before acting.
8. Deletion / your rights
On request, and always on offboarding, a client's data is exported in standard formats and then deleted from the operational database and from future backups (older encrypted backups age out under the backup retention window). See Offboarding for the process and the rehearsed handover.
*Related: Service Level Agreement · Offboarding · our how-it-works notes · our internal incident runbook. The binding privacy policy and DPA are separate solicitor-drafted documents; this overview must be reconciled with them.*