Sub-processors
Status: accurate as of 2026-08-25, maintained against the code. A sub-processor here means: a third party that can receive personal data (or data derived from it) from the Leasebook deployment in the course of normal operation. This list is deliberately short and each entry states exactly what it receives, so it can be checked against the source it cites rather than taken on faith.
| Sub-processor | What it receives | Purpose | Consent basis | Source |
|---|---|---|---|---|
| Google (Address Validation API / Places API) | A property address string entered into the product, and the country. Returns a canonicalised address, postcode, coordinates and administrative area, which is stored on the property record. | Resolving a typed address to a validated, canonical one. | Processor's legitimate operational use — this runs whenever an address is entered/verified; it is not gated by a separate per-run tenant/owner consent screen today. | address-verification.mjs; restricted to the two named Google APIs (docs/OPERATIONS.md). |
| Open-Meteo | Property coordinates (derived from the address, not the address text itself). | Weather-related context for a property. | Same basis as above — an operational lookup, not tenant personal data. | docs/SECURITY.md ("Weather / news"); fixed hosts only, never a request-supplied URL. |
| Cloud AI provider (gap-fill / validation), operator-configured | Minimised research context for public property research only — claim/source IDs, public URLs, conflicts, research questions and candidate search queries. Explicitly excludes fetched page bodies, uploaded-document bodies, contacts, credentials and financial accounts (docs/MVP-PLAN.md, Pipeline v13). Tenant and owner-client personal data is not part of what this path can send, by construction of the minimized export it builds. | Bridging gaps in public research (e.g. finding a missing public fact about a property) and validating findings, where local inference cannot. | Off by default and consent-gated per run. The export policy's provider defaults to "disabled" (public-export-policy.mjs); a specific provider only activates on an explicit consent decision with an operator-set cost budget, and is dormant in production until deliberately turned on (docs/MVP-PLAN.md: "zero-default monthly budget... keep production dormant until explicitly activated"). No specific commercial vendor is contracted or named as fixed — this is a pluggable, operator-chosen provider, disabled by default. | public-export-policy.mjs, mcp.mjs, docs/RESEARCH-ENGINE-DIRECTION.md §3 ("Cloud reasons, local indexes"). |
| Resend (email delivery) — chosen 2026-08-25, not yet live | The recipient's email address and the rendered message content. For a tenant or owner-client that means their name, their address, and whatever the message says about their tenancy — rent due, a document expiring, a work order. | Sending the invitations that are the only way anyone gets an account, plus reminders, digests and statements. | Governed by the agency's own notification settings (organisation_notification_settings) and, for anyone outside the agency, by three independent gates that must all agree (ALLOW_EXTERNAL_EMAIL, the organisation setting, and a per-contact opt-out no message type can override). | mailer.mjs (sendViaResend). |
What is explicitly not a sub-processor today
- Local AI extraction (Ollama) does not leave the deployment — it runs on the same server, is operator-configured, and only ever sees minimised, address-withheld context (
docs/SECURITY.md). It is infrastructure we run, not a third party we send data to. synthesizePropertyAnswer(the "ask your property" feature) is bound to the local model specifically so a tenant's private evidence bundle never leaves the box (docs/RESEARCH-ENGINE-DIRECTION.md, decision recorded 2026-07-24).- Tenant photographs are never sent to any cloud model, full stop — this is a standing decision recorded in
docs/SECURITY.md("Photographs are the sharpest edge"), not merely an unimplemented feature. - Any URL a user pastes or a source page points us at goes through the guarded fetch path (
fetchGuarded()infetcher.mjs) described indocs/SECURITY.md's SSRF threat model. That guard restricts what can be reached at all (scheme allowlist, private-IP blocking, bounded response, no blind redirects); it is a safety control on outbound requests, not a list of sub-processors the guard is designed to reach.
Email delivery — current state honestly
Resend was chosen on 2026-08-25 and is not live yet. Both halves of that matter, so both are stated.
mailer.mjs supports two backends behind EMAIL_BACKEND. It defaults to smtp, and in the current deployment that points at Mailpit, a development mail-catcher — no message reaches a real inbox today. The Resend backend is a complete implementation rather than a stub (it posts to the Resend API, and a non-2xx response throws rather than being swallowed), but it has never run against the live API. Until the account exists, the domain is verified and RESEND_API_KEY is set, "email delivery" above should be read as not yet live to real recipients — which an agency evaluating tenant notifications should know before relying on them.
One open question, deliberately not answered here. Resend is a US company. The production deployment is being placed in the EU specifically so tenant personal data stays there (docs/HOSTING-MIGRATION-PLAN.md, decision 2), and email is the one path that carries a tenant's name and address to a third party by design. Whether Resend is pinned to an EU region, and on what transfer basis, must be settled — and recorded in this table — before the first real send. It is a disclosure either way, not necessarily a blocker; what would be wrong is a customer's data-protection officer discovering it before we did.
Keeping this list honest
This file is reviewed whenever a new outbound integration is added anywhere in the codebase (a new *.mjs service module reaching an external host, or a new EMAIL_BACKEND/AI provider being wired up for real). If you are adding such an integration and this file was not part of your change, that is a gap — flag it rather than shipping a sub-processor this document doesn't know about.