Sub-processors
Draft wording — not yet reviewed by a solicitor. This page is published so you can see how we intend to work while a solicitor reviews it. Anything in [square brackets] is still to be decided. It is not final legal wording and should not be relied on as such. Version recorded: draft-2026-11-03.
How this list is organised
The code reaches outside vendors in four different ways, and the legal answer may differ for each. So the list is split four ways:
- A. Platform sub-processors. AquaCRM engages these itself, on its own account, for every customer. Most of them handle every customer's data. A few are reached only through particular features, but those features use AquaCRM's own account with the vendor, not the customer's.
- B. Connected Services. The customer connects these with its own account and credentials, and AquaCRM's code calls them on the customer's instruction. The draft DPA (clause 7.4) proposes that they are the customer's own processors, not AquaCRM's sub-processors. That is a proposal for the solicitor, not a settled position.
- C. Planned, not active. The backup system will use these once it is switched on.
- D. Not sub-processors, said out loud, so a reviewer does not think they were missed.
Why category B can be separated from A. For most providers, the code uses the deployment's own keys only for the operator's own agency. For every other agency, a feature that needs the provider does not work until the agency connects its own account. Three providers are exceptions. Some of their code paths use the deployment's own account for any agency, so they appear in table A for those uses and in table B for the rest:
- Resend, for the verification email sent when a new agency signs up;
- GitHub, for publishing a client project and for the code editor, wherever the agency has not connected its own GitHub account;
- Vercel, for client-project preview deployments, wherever the agency has not connected its own Vercel account.
See reviewer note 3.
A. Platform sub-processors
| Sub-processor | What it does for AquaCRM | Personal data it receives | Region | Region verified? | Transfer basis (proposed — to confirm) | Status |
|---|---|---|---|---|---|---|
| Railway | Hosts the application server, which renders every page and runs every API request | Everything the Service processes passes through it in use. It also holds the server logs, which can contain the text of errors, and that text can include personal data (DPA Annex 2, item 18) | EU West | Yes, per the in-product register | Processing in the EEA: UK adequacy regulations for the EEA. Confirm whether Railway staff outside the UK or EEA can access it, and which DPA or transfer terms Railway offers. | In use since September 2026, when it replaced Vercel as the host |
| Supabase | Database, user authentication and file storage | All stored Workspace data, sign-in identities, and uploaded files: documents, CVs, images, video, and audio (calls recorded in the inbox, and voice notes) | EU, eu-west-1 (Ireland) | Yes, per the in-product register | As for Railway: EEA adequacy for storage. Confirm Supabase's DPA and any access from outside the UK or EEA. | In use. There is one project only, and it serves development, staging and production |
| Sentry | Error monitoring | The error's message and stack trace, which can contain personal data. Also the internal workspace, client and user ids attached to the report | EU (Germany), intended | No. The region is set by the address in the deployment's DSN (SENTRY_DSN, or NEXT_PUBLIC_SENTRY_DSN when that is the only one set), and the live Railway value is not recorded in the repository | EEA adequacy if the EU region is confirmed. Confirm Sentry's DPA. | Unknown. The SDK ships with the app, and reports are sent only if SENTRY_DSN or NEXT_PUBLIC_SENTRY_DSN is set on the server. The repository does not record whether either is set on the live service |
| Cloudflare — Turnstile | Bot protection on sign-in, sign-up, password reset, magic-link requests and the public enquiry forms (not on form capture or the demo-interest form, which have rate limits only) | The visitor's or user's IP address, and the challenge token | Global (anycast) | No | To confirm: the UK Extension to the EU-US Data Privacy Framework, if Cloudflare is certified, or the IDTA or UK Addendum in Cloudflare's DPA | In use wherever this build's production sign-in works. In production the code refuses sign-in, sign-up, password-reset and magic-link requests when Turnstile is not configured, rather than letting them through. The in-product register still says "not enabled". That line is out of date |
| Resend — sign-up verification email | Sends the email that verifies a new agency's address when it signs up. This one email goes through the deployment's own Resend account for every new agency, whichever agency it is for | The email address of the person signing up, and their verification link | US / EU, set per account | No. The sending region is chosen in the Resend account, which the app cannot read | To confirm | In use where the deployment has a Resend key and an auth sender address. The register records RESEND_API_KEY as set on the deployment. Every other email goes through the agency's own connection (table B) |
| GitHub — client projects and the code editor | Creating a private repository for a client project and pushing the whole project directory to it. In the code editor, reading a project's repository and committing branches and pull requests back to it. Where the agency has not connected its own GitHub account, publishing uses the deployment's own GitHub token, for any agency. The code editor does the same where the project also has no token of its own, but only when the person using it is an agency owner, of any agency | Whatever the project's files contain. Nothing limits what an agency puts in a project | US / global | No | To confirm | Configured. The register records GITHUB_TOKEN and GITHUB_OWNER as set on the deployment, so the operator's own GitHub account is the default target. Whether any customer's project has been pushed to it is not recorded. Publishing needs an agency role with manage access to that client's systems, and the code-editor fallback needs an agency owner. Nothing checks that the agency is the operator's own |
| Vercel — client-project preview deployments | Uploading a client project's files to create a preview deployment. Where the agency has not connected its own Vercel account, this uses the deployment's own Vercel token, for any agency | Whatever the project's files contain | US / global | No | To confirm | Configured. The register records VERCEL_TOKEN in the project's environment files. Which variables the live service carries is not recorded. Reachable by any agency role with manage access to that client's systems |
| Google — sign-in and Calendar | "Sign in with Google" for Authorised Users, and Calendar sync for a user who connects their own Google calendar. Both use the deployment's own Google OAuth client | Sign-in: the user's Google account email, name and account id. Calendar: appointment times, titles and attendees | US / global | No | To confirm | Not enabled, according to the register, which records no Google OAuth credentials on the deployment. Google becomes a platform sub-processor for these two surfaces the day those credentials are set |
| Vercel — Blob storage (legacy) | The fallback store for private uploads. It is used only where Supabase storage is not configured, and it is still read for any file stored there before the move to Supabase | Uploaded files stored before the move, if any | US / global | No | To confirm | Legacy. Whether any customer files remain in it is not recorded. Ed to check, and to empty it if nothing needs it. |
B. Connected Services (the customer's own accounts)
The customer creates the account with each of these providers and gives AquaCRM the credential. AquaCRM stores the credential encrypted when it is entered through the integration, calendar or inbox connections, a module's secret settings field, or, from [the date the Email sender change is deployed], the Email sender module's own Settings page. DPA Annex 2 item 4 names the exceptions: a secret typed into an automation's webhook headers or a website-editor webhook target is stored in plain text, and so is every Email sender credential until that date, and one stored before it until it is re-encrypted. AquaCRM sends data to the provider only when the customer uses the feature. The provider's region is whatever the customer's own account is set to. The regions below come from the register and describe the provider's default position. None of them has been verified.
| Service | What AquaCRM sends or receives on the customer's instruction | Region (register; not verified) |
|---|---|---|
| Resend (email) | Every email except the sign-up verification email in table A: the recipient's address and the whole message. This includes the workspace's own password-reset and magic-link sign-in emails, which are not sent at all until the workspace connects an email provider. For monthly performance reports, Resend also posts delivered, bounced or complained events to AquaCRM; the matching provider email id is read and the event id, result and provider/receipt times are retained against the exact report delivery | US / EU, set per account |
| The customer's own SMTP server | The recipient's address and the whole message. AquaCRM cannot name the mailbox provider behind it | Customer's provider |
| Postmark (email) | Outbound: the recipient's address and the whole message. Inbound: Postmark posts each email sent to the workspace's inbound address to AquaCRM's webhook as the whole message, attachments included. AquaCRM keeps the sender's address and display name; every To, Cc and original-recipient address, including third parties who were only copied in; the subject; a plain-text body (HTML is reduced to text and never kept); each attachment's file name, content type and size; the Message-ID (or Postmark's own message id), In-Reply-To and References headers that thread the conversation; when it was sent and when it arrived; the person and client the sender's address was matched to, where it matches exactly one; which workspace and inbound connection received it; whether staff have read or resolved it, when, and who resolved it. Those lists have limits: AquaCRM keeps up to 50 recipient addresses, counting only those that read as addresses, lower-cased and without repeats; up to 50 attachments, a nameless one listed as "attachment"; and the last 30 References entries. It cuts the subject to 300 characters and the body to 50,000. Attachment content is never stored | US |
| The customer's own mailbox (IMAP) | AquaCRM signs in to the mailbox and reads incoming messages from the chosen folder. AquaCRM cannot name the mailbox provider | Customer's provider |
| Stripe | Checkout sessions, payment requests and webhooks, carrying the payer's name, email and amounts. Also Stripe Connect events for affiliate payouts | US / global |
| OpenAI | For the in-app assistant: the prompt, and whatever workspace records are given to it. For the code editor's assistant: the conversation in that project's thread, on a key set for that project alone. Both send requests with OpenAI's store: false setting | US / global |
| Twilio | SMS, WhatsApp and voice: the phone numbers, and the text of each message. Voice calls, outgoing and incoming, are connected by Twilio: for an outgoing call it rings the staff member's phone and then dials the customer, and an incoming call is forwarded to a staff phone. So the live call audio passes through Twilio. AquaCRM does not ask Twilio to record calls. Twilio's call webhooks send AquaCRM the caller's number and the call's status | US / global |
| Meta (Facebook and Instagram) | Social inbox: the sender's profile identity and the content of each message, in both directions | US / global |
| Google — Search Console | The client website being reported on, and its search metrics | US / global |
| Google — Places (Sales scouting) | The search terms staff type, which name a trade and an area | US / global |
| GitHub | Once the agency connects its own GitHub account: the same publishing and code-editor uses as in table A, on the agency's account instead of AquaCRM's | US / global |
| Vercel — deployments | Once the agency connects its own Vercel account: preview deployments as in table A, on the agency's account. (The code also contains a helper that adds a client's custom domain to Vercel, but nothing calls it) | US / global |
| Scouting automation (the customer's own n8n workflow or agent) | A Scouting brief carries no client or prospect data. An Enrichment request carries one prospect business's details. A question to the team carries the question, who asked it and, if they tick the box, the prospect they are looking at | Wherever the customer runs it |
| Automation and website-form webhooks | Whatever the automation is set up to send, or the fields of a website form whose submissions go to a webhook, posted to the address the customer chose | Wherever that address is |
| The client's own Supabase project or shop ("Client Supabase", "Client shop") | Form submissions and orders stay in the client's own database. AquaCRM receives a signed pointer, then reads one record by id when someone opens it. It holds narrow signing secrets and, for a shop, the client's publishable key, which is the public key that is safe inside a web page. It never holds a secret database key: the connection refuses service-role and secret keys | The client's own project |
Resend, GitHub and Vercel appear in both tables. Table A covers the paths that use AquaCRM's own account. Table B covers the same provider once the customer connects its own. For GitHub and Vercel, that split is how the code behaves today, not a decision anyone has made. See open question E6.
The operator's own agency. For the operator's own agency only, the deployment's own keys are used where no connection has been made. That covers Resend, Stripe and OpenAI, for example. For that one agency, these providers are the operator's processors. This does not affect customers.
C. Planned — will process data once the backup system is switched on
The off-database backup is built and switched off. Once it is switched on:
| Recipient | What it would hold | Notes |
|---|---|---|
| GitHub (Actions) | It runs the nightly backup job, and keeps the encrypted backup file as a run artifact for 90 days | Encrypted to a public certificate. The private key never leaves the operator |
| Resend | The encrypted backup as an email attachment, plus failure alerts | Encrypted. The README says not to treat the inbox as the archive |
| [OBJECT STORE — not yet chosen; the README suggests R2, B2 or S3] | The durable archive of encrypted backups | Ed to choose. Region to be recorded here. |
[Solicitor: does a vendor that holds only data encrypted with a key it never sees need to be listed as a sub-processor?]
D. Not sub-processors, said out loud
- The malware scanner is AquaCRM's own. Uploaded files are scanned by a ClamAV service that AquaCRM runs itself; the register records it as running in the same Railway project as the application. It reads the uploads sent to it for checking, and it is not a third party. But files do cross the public internet to reach it. The code will not send a file to a private address: it refuses private and unique-local addresses, which is how Railway's private network is addressed [per Railway's own description, not checked here]. So the scanner is reached at its public address. Of the two ways a scanner can be configured, one requires HTTPS in production; the other uses HTTPS only if the configured address does. Which one the live service uses is not recorded. No third party receives the files either way. See reviewer note 7.
- Tools the customer adds to its own websites. The Aqua Tag can load analytics and advertising tools that the customer configures for its sites, such as Google Analytics, Google Tag Manager, PostHog or advertising pixels. Each tool waits for the cookie category the customer assigns to it. By default analytics tools are in "analytics" and advertising pixels in "marketing", so they wait for the visitor's consent. But the customer can put any tool in "necessary", and then it loads before the visitor has made any choice. The Search Console verification tag is "necessary" by default. These tools load in the visitor's browser and send data directly to that vendor, under the customer's own arrangement with it. AquaCRM does not receive what they collect.
- The map in Sales scouting. The prospecting screens embed a Google map. The staff member's browser requests it from Google every time the map is shown. If the customer has connected a Google Maps key, the request carries that key and the prospect's Google place id where one is recorded, or else the prospect business's name and address. If it has not, the request carries the name and address, under no account at all. [Solicitor to classify: Google is probably an independent controller here, but the customer should be told about it.]
- Google's public STUN servers, in hosted meetings. When someone joins a meeting
room at
/m/..., their browser contacts Google's public STUN servers (stun.l.google.com) to discover its own public IP address, so Google receives that IP address. The call itself is peer to peer, so participants' browsers also learn each other's IP addresses. There is no relay server. [Solicitor to classify: as with the map, Google is probably not AquaCRM's processor here, but participants should be told. See open question S4.] - Shopify is not used. The e-commerce module contains a Shopify client, but nothing calls it and there is no way to connect it. It is not a sub-processor until it is wired up.