Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions apps/backend/src/services/invite-mailer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
19 changes: 1 addition & 18 deletions apps/backend/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
Loading