diff --git a/apps/backend/src/services/invite-mailer.ts b/apps/backend/src/services/invite-mailer.ts index 77bbb44f2..cd1f3679a 100644 --- a/apps/backend/src/services/invite-mailer.ts +++ b/apps/backend/src/services/invite-mailer.ts @@ -7,9 +7,11 @@ import { Config, Context, Effect, Layer, Schema } from "effect" */ /** - * The domain invitations are sent from. The stack registers it as a Cloudflare Email Sending - * subdomain (`worker.ts`); a sender on a domain that is not onboarded can only reach verified - * addresses ("destination address is not a verified address"). + * The domain invitations are sent from. It is onboarded to Cloudflare Email Sending as a subdomain + * of the hazel.sh zone (DKIM, SPF and return-path records live on the zone). That is done once in + * the dashboard, not by the stack: the deploy token has no Email Sending permission. A sender on a + * domain that is not onboarded can only reach verified addresses ("destination address is not a + * verified address"). */ export const INVITE_EMAIL_DOMAIN = "mail.hazel.sh" diff --git a/apps/backend/src/worker.ts b/apps/backend/src/worker.ts index b637f3912..4ac44529c 100644 --- a/apps/backend/src/worker.ts +++ b/apps/backend/src/worker.ts @@ -5,7 +5,7 @@ * class tags of the previous deploy. */ import { bindBotGateways } from "@hazel/bot-gateway/object" -import { HazelStack, hazelWorkerProps, stageProps, ZONE } from "@hazel/infra/cloudflare" +import { HazelStack, hazelWorkerProps, stageProps } from "@hazel/infra/cloudflare" import { cachedRecoverable } from "@hazel/infra/cached-recoverable" import { hazelTelemetry } from "@hazel/infra/maple" import { isolateContext } from "@hazel/infra/worker-http" @@ -17,7 +17,6 @@ import { DiscordGatewayObjectLive, } from "./worker/discord-gateway-object" import { HTML_RENDER_PENDING_MAX_AGE_SECONDS, HTML_RENDER_PENDING_PREFIX } from "./services/html-renders" -import { INVITE_EMAIL_DOMAIN } from "./services/invite-mailer" import { apiEnv } from "./worker/env" import { buildApp, makeFetch } from "./worker/http" import { MessageLiveObject, MessageLiveObjectLive } from "./worker/live/message-live-object" @@ -62,28 +61,12 @@ export const HtmlRendersBucket = Cloudflare.R2.Bucket( })), ) -/** - * Onboards the invitation sender's domain to Cloudflare Email Sending (DKIM, SPF and return-path - * records are created on the zone). Without it the `EMAIL` binding only reaches verified - * addresses. One per zone, so only prd declares it; every stage sends from it. - */ -const InviteSendingSubdomain = Effect.gen(function* () { - const { accountId } = yield* yield* Cloudflare.CloudflareEnvironment - const zone = yield* Cloudflare.Zone.findZoneByName({ accountId, name: ZONE }) - if (zone === undefined) return yield* Effect.die(new Error(`Cloudflare zone ${ZONE} not found`)) - return yield* Cloudflare.Email.SendingSubdomain("invite-sending-subdomain", { - zoneId: zone.id, - name: INVITE_EMAIL_DOMAIN, - }) -}).pipe(Effect.orDie) - /** `__ALCHEMY_RUNTIME__` folds to `true` in the bundle, so the stack-side branch is tree-shaken. */ const props = Effect.gen(function* () { if (globalThis.__ALCHEMY_RUNTIME__) return { main: import.meta.url } const stack = yield* HazelStack const cache = yield* ApiCache const htmlRenders = yield* HtmlRendersBucket - if (stack.stage.kind === "prd") yield* InviteSendingSubdomain const email = yield* Cloudflare.Email.SendEmail(EMAIL_BINDING) return { main: import.meta.url,