From d4e822702149c9d2d7120874c4065ae56b17d8f8 Mon Sep 17 00:00:00 2001 From: Ismael Leon Date: Sun, 20 Sep 2026 14:10:15 -0600 Subject: [PATCH] feat(contact): publish info@cloudils.com in the footer and legal pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both legal pages promised a way to reach us and then pointed at "the address listed on the project repository", which is not an address. The privacy policy in particular offers a right to erasure, and a right you exercise by going to find a link is not much of one. info@cloudils.com is now a Cloudflare Email Routing alias forwarding to a personal mailbox: the private address stays out of public HTML, and the destination can move without a deploy. Routing only — the app sends no mail, so Email Sending is deliberately not onboarded, and the catch-all stays on drop so guessed addresses do not reach the inbox. The address lives in one constant because the footer and the two legal documents have to agree; a stale copy in one of them would be a broken promise rather than a typo. Both "last updated" dates move with the change, as those pages tell the reader they will. `rel="external"` on the mailto links hands the click to the mail client instead of the SvelteKit router, which is also what lets the no-navigation-without-resolve rule see them for what they are. Closes #90 --- docs/contact-email.md | 87 +++++++++++++++++++++++++++++ src/lib/components/ui/Footer.svelte | 20 ++++++- src/lib/contact.ts | 16 ++++++ src/routes/privacy/+page.svelte | 9 ++- src/routes/terms/+page.svelte | 9 ++- 5 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 docs/contact-email.md create mode 100644 src/lib/contact.ts diff --git a/docs/contact-email.md b/docs/contact-email.md new file mode 100644 index 0000000..e44e897 --- /dev/null +++ b/docs/contact-email.md @@ -0,0 +1,87 @@ +# The contact address + +How `info@cloudils.com` reaches a real inbox, and how to point another site at +the same mailbox. + +## Why an alias and not a personal address + +A personal address in a page footer is a personal address in every scraper's +list within a week. An alias on the apex domain gives the same reachability +with three properties a raw address does not have: + +- the private destination never appears in public HTML, or in this repository; +- the destination can change without touching a deploy; +- one address serves every site on the domain, so a second project does not + need a second mailbox. + +Cloudflare Email Routing is the cheapest way to get all three: it is free, it +needs no mail server, and forwarding is a rule on the zone rather than code. + +## What is configured + +On the `cloudils.com` zone: + +| Piece | Value | +| ------------------- | ------------------------------------------------------------- | +| Routing rule | `to: info@cloudils.com` → forward to the personal address | +| Catch-all | Disabled, action `drop` | +| Destination address | Verified once from Cloudflare's confirmation mail | +| DNS | Three `route*.mx.cloudflare.net` MX records, SPF and DKIM TXT | + +Routing is receive-only. Nothing in this app sends mail, so Email **Sending** +is deliberately not onboarded — replies go out from the personal mailbox, from +its own address. That is a visible seam (you write to `info@`, the reply comes +from somewhere else), and the price of not running a mail server. + +The catch-all stays on `drop` on purpose. A catch-all that forwards means every +dictionary-attack guess at `@cloudils.com` lands in the destination inbox, and +the whole point of the alias was to keep that inbox quiet. + +## Reusing it on another site + +The address is domain-wide, not app-specific, so another site on +`cloudils.com` needs no Cloudflare change at all — put the same `mailto:` in +its footer and it works. + +Only two cases need work: + +**A site on a different domain.** Repeat the setup on that zone: + +```bash +npx wrangler email routing addresses create you@example.com # then click the verification mail +npx wrangler email routing enable newdomain.com # adds the MX, SPF and DKIM records +npx wrangler email routing rules create newdomain.com \ + --name "info forwarding" \ + --match-type literal --match-field to --match-value "info@newdomain.com" \ + --action-type forward --action-value "you@example.com" +``` + +Check for existing `MX` records on the apex before enabling: `routing enable` +installs Cloudflare's own, and a domain already receiving mail elsewhere will +stop receiving it. `nslookup -type=MX newdomain.com` answers that in a second. + +A destination address is account-scoped, so one that is already verified can be +reused by a new zone without another confirmation mail. + +**A per-project address**, if you ever want to filter by recipient — add a +second rule (`nextsode@cloudils.com`, say) pointing at the same destination. +Rules are literal matches, so they cost nothing until they exist. + +## Changing where the mail lands + +Add and verify the new destination, then repoint the rule: + +```bash +npx wrangler email routing rules list cloudils.com # note the rule id +npx wrangler email routing rules update cloudils.com \ + --action-type forward --action-value "new@example.com" +``` + +The published address does not change, so no deploy is involved. + +## In the app + +`src/lib/contact.ts` holds the address; the footer and both legal pages import +it. It is one constant because a privacy policy that promises a right to +erasure is only worth the address it gives you — a stale copy in one of the +three is a broken promise, not a typo. diff --git a/src/lib/components/ui/Footer.svelte b/src/lib/components/ui/Footer.svelte index 754323f..b374a78 100644 --- a/src/lib/components/ui/Footer.svelte +++ b/src/lib/components/ui/Footer.svelte @@ -1,9 +1,10 @@