Skip to content

feat(email): replace Resend with Cloudflare Email Service#150

Merged
Makisuo merged 1 commit into
mainfrom
chore/cloudflare-email-service
Jun 30, 2026
Merged

feat(email): replace Resend with Cloudflare Email Service#150
Makisuo merged 1 commit into
mainfrom
chore/cloudflare-email-service

Conversation

@Makisuo

@Makisuo Makisuo commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

What

Replaces Resend with Cloudflare Email Service ("Email Sending", public beta) for all transactional email — the onboarding sequence and the weekly digest. The Cloudflare account already has noreply.maple.dev onboarded (DNS configured, enabled, 1000/day quota), so delivery now goes through the native EMAIL worker binding — no API key/secret.

Why

Drop the external Resend dependency (API key secret + raw HTTP call) in favor of the platform-native sender, with zero changes to email content or send-site logic.

Changes

  • apps/api/src/lib/EmailService.ts — resolves the send_email binding from WorkerEnvironment and calls env.EMAIL.send({ from, to, subject, html, replyTo }). The public send(to, subject, html, replyTo?) signature and EmailDeliveryError are unchanged, so OnboardingEmailService / DigestService and the React Email templates are untouched. isConfigured now reflects binding presence; 15s timeout, PII rules, and logging preserved; span email.provider="cloudflare" + email.message_id.
  • apps/api/src/lib/Env.ts — drop RESEND_API_KEY / RESEND_FROM_EMAIL, add EMAIL_FROM (default Maple <notifications@noreply.maple.dev>).
  • alchemy + wrangler (api + alerting) — add the EmailSender / send_email binding, swap RESEND_FROM_EMAILEMAIL_FROM, remove the RESEND_API_KEY secret.
  • apps/alerting/src/worker.ts — feed WorkerEnvironment.layer into EmailServiceLive (the api worker already provides it at app scope).
  • Cleanup: stale comment in OnboardingEmailService.ts, .env.local, README.md.

Sender identity unchanged: From noreply.maple.dev, Reply-To david@maple.dev on the founder/onboarding emails only.

Verification

  • bun typecheck — all 24 packages pass.
  • bun run --cwd apps/api test — 631/631 pass. Outside a worker isolate EMAIL is undefined → isConfigured=false, sends skip (no regression).

Follow-up (needs a deploy)

  • Deploy to a PR-preview/staging stage, confirm the EMAIL binding attaches, fire one real send, and check the Email Sending dashboard counter + delivery. Watch for E_SENDER_NOT_VERIFIED (would mean EMAIL_FROM/allowedSenderAddresses don't match the onboarded noreply.maple.dev domain).
  • Note: wrangler configs use remote: true, so wrangler dev sends through the real Cloudflare sender; pure bun dev stays a no-op.

🤖 Generated with Claude Code

Route all transactional email (onboarding sequence + weekly digest)
through the Cloudflare Email Service `EMAIL` worker binding instead of
Resend's HTTP API.

- EmailService resolves the `send_email` binding from WorkerEnvironment
  and calls env.EMAIL.send({ from, to, subject, html, replyTo }); the
  public send() signature is unchanged so onboarding/digest logic and
  email templates are untouched. No API key/secret needed.
- Env: drop RESEND_API_KEY/RESEND_FROM_EMAIL, add EMAIL_FROM.
- alchemy + wrangler: add EmailSender/send_email binding for api +
  alerting, swap RESEND_FROM_EMAIL -> EMAIL_FROM, remove RESEND secret.
- Sender identity unchanged: From noreply.maple.dev, Reply-To
  david@maple.dev on the founder/onboarding emails.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@pullfrog

pullfrog Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Your LLM provider API key was rejected. Rotate the key in your provider dashboard, then update the matching GitHub Actions secret.

Update repo secret → · Model settings → · Setup docs → · Ask in Discord →

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Rerun failed job ➔View workflow run | via Pullfrog | Using Claude Opus𝕏

@Makisuo Makisuo merged commit 9961f89 into main Jun 30, 2026
6 checks passed
@Makisuo Makisuo deleted the chore/cloudflare-email-service branch June 30, 2026 17:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant