Skip to content

refactor: remove unused Customer.io transactional email codepath#1498

Open
evanjacobson wants to merge 6 commits intomainfrom
improvement/remove-customerio-email
Open

refactor: remove unused Customer.io transactional email codepath#1498
evanjacobson wants to merge 6 commits intomainfrom
improvement/remove-customerio-email

Conversation

@evanjacobson
Copy link
Contributor

@evanjacobson evanjacobson commented Mar 24, 2026

Summary

Removes the unused Customer.io transactional email codepath. Production has been on Mailgun.

  • Deleted email-customerio.ts and removed customerio-node dependency
  • Removed EMAIL_PROVIDER config and routing logic — send() always uses Mailgun
  • Simplified admin email testing page (removed provider selector, CIO variable preview)
  • Removed dead templates object (CIO template ID mapping), made subjects the single source of truth
  • Simplified creditsVars (removed CIO Liquid-only variables)

Note: external-services.ts CIO user deletion is preserved — it uses different API keys (CUSTOMERIO_SITE_ID/CUSTOMERIO_API_KEY) for marketing audience cleanup.

Verification

  • npx tsc --noEmit — no type errors
  • pnpm lint — clean
  • Verified via automated agent that this branch has zero NeverBounce contamination (9/9 checks pass)
  • Admin email testing page loads with no provider dropdown (manually tested via Playwright)

Visual Changes

Before After
Admin email testing page has Template / Provider / Recipient selectors (3-column grid) Template / Recipient only (2-column grid), no provider dropdown

Reviewer Notes

Production has been on Mailgun. Removes email-customerio.ts, customerio-node
dependency, EMAIL_PROVIDER config, and CIO provider option from admin
email testing page. CIO user deletion in external-services.ts is
preserved (different API keys, used for marketing audience cleanup).
@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Mar 24, 2026

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0

Fix these issues in Kilo Cloud

Issue Details (click to expand)

N/A

Other Observations (not in diff)

Issues found in changed files outside the diff and therefore not eligible for inline comments:

File Line Issue
kiloclaw/src/routes/platform.ts 743 /api/platform/config/restore returns raw error text instead of going through sanitizeError(), which can leak controller or file-system details back to callers.
Files Reviewed (80 files)
  • cloudflare-gastown/src/dos/Agent.do.ts
  • cloudflare-gastown/src/dos/Town.do.ts
  • cloudflare-gastown/src/dos/town/container-dispatch.ts
  • cloudflare-gastown/src/dos/town/conversation.ts
  • cloudflare-gastown/test/unit/conversation.test.ts
  • kiloclaw/controller/src/config-writer.test.ts
  • kiloclaw/controller/src/config-writer.ts
  • kiloclaw/controller/src/routes/gateway.ts
  • kiloclaw/src/durable-objects/gateway-controller-types.ts
  • kiloclaw/src/durable-objects/kiloclaw-instance.test.ts
  • kiloclaw/src/durable-objects/kiloclaw-instance/fly-machines.ts
  • kiloclaw/src/durable-objects/kiloclaw-instance/gateway.ts
  • kiloclaw/src/durable-objects/kiloclaw-instance/index.ts
  • kiloclaw/src/durable-objects/kiloclaw-instance/reconcile.ts
  • kiloclaw/src/durable-objects/regions.ts
  • kiloclaw/src/routes/platform.ts
  • packages/db/src/migrations/0059_calm_gressill.sql
  • packages/db/src/migrations/meta/0059_snapshot.json
  • packages/db/src/migrations/meta/_journal.json
  • packages/db/src/schema.ts
  • packages/trpc/dist/index.d.ts
  • src/app/(app)/claw/components/ChannelPairingStep.tsx
  • src/app/(app)/claw/components/ClawDashboard.tsx
  • src/app/(app)/claw/components/InstanceControls.tsx
  • src/app/(app)/claw/components/PermissionPresetCards.tsx
  • src/app/(app)/claw/components/PermissionStep.tsx
  • src/app/(app)/claw/components/ProvisioningStep.tsx
  • src/app/(app)/claw/components/SettingsTab.tsx
  • src/app/(app)/claw/components/claw.types.ts
  • src/app/(app)/claw/components/withStatusQueryBoundary.test.ts
  • src/app/(app)/profile/page.tsx
  • src/app/admin/bulk-credits/page.tsx
  • src/app/admin/components/AppBuilder/AppBuilderProjectDetail.tsx
  • src/app/admin/components/AppBuilder/AppBuilderProjectsTable.tsx
  • src/app/admin/components/Deployments/DeploymentsTable.tsx
  • src/app/admin/components/KiloclawDashboard.tsx
  • src/app/admin/components/KiloclawInstances/KiloclawInstanceDetail.tsx
  • src/app/admin/components/KiloclawInstances/KiloclawInstancesPage.tsx
  • src/app/admin/components/KiloclawRegions/KiloclawRegionsPage.tsx
  • src/app/admin/components/KiloclawRegions/region-constants.ts
  • src/app/admin/oss/page.tsx
  • src/app/api/openrouter/[...path]/route.ts
  • src/app/api/openrouter/embeddings/route.ts
  • src/components/organizations/OrganizationMembersCard.tsx
  • src/components/organizations/byok/BYOKKeysManager.tsx
  • src/components/profile/EditProfileDialog.tsx
  • src/components/profile/UserProfileCard.tsx
  • src/hooks/useKiloClaw.ts
  • src/lib/kiloclaw/instance-registry.ts
  • src/lib/kiloclaw/kiloclaw-internal-client.ts
  • src/lib/kiloclaw/types.ts
  • src/lib/llm-proxy-helpers.ts
  • src/lib/processUsage.messages.ts
  • src/lib/processUsage.responses.ts
  • src/lib/processUsage.shared.ts
  • src/lib/processUsage.ts
  • src/lib/processUsage.types.ts
  • src/lib/providerHash.test.ts
  • src/lib/providerHash.ts
  • src/lib/providers/getEmbeddingProvider.test.ts
  • src/lib/providers/google.ts
  • src/lib/providers/index.ts
  • src/lib/providers/kilo-free-model.ts
  • src/lib/providers/openrouter/index.ts
  • src/lib/providers/openrouter/inference-provider-id.ts
  • src/lib/providers/openrouter/sync-providers.ts
  • src/lib/providers/provider-definitions.ts
  • src/lib/providers/types.ts
  • src/lib/providers/vercel/mapModelIdToVercel.ts
  • src/lib/user.server.ts
  • src/lib/user.ts
  • src/routers/admin-kiloclaw-instances-router.ts
  • src/routers/admin-kiloclaw-regions-router.ts
  • src/routers/admin-router.ts
  • src/routers/byok-router.ts
  • src/routers/kiloclaw-router.ts
  • src/routers/user-router.ts
  • src/scripts/byok/test.ts
  • src/tests/openrouterApi.timeout.test.ts
  • storybook/stories/claw/ChannelPairingStep.stories.tsx

Reviewed by gpt-5.4-20260305 · 2,171,694 tokens

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most of these will be filtered out after #1356 merges

The jest.mock for @/lib/email only provided sendOrganizationInviteEmail,
but the email-testing-router (loaded transitively via the router tree)
now imports subjects from @/lib/email at module level. Add the missing
exports to the mock.
…-Org/cloud into improvement/remove-customerio-email
// Mock the email service to prevent actual API calls during tests
jest.mock('@/lib/email', () => ({
sendOrganizationInviteEmail: jest.fn().mockResolvedValue(undefined),
subjects: { orgInvitation: 'Kilo: Teams Invitation' },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have these additions in this PR?

evanjacobson added a commit that referenced this pull request Mar 25, 2026
)

## Summary

Adds NeverBounce email verification before sending transactional emails
to reduce our ~3% bounce rate. Blocks `invalid` and `disposable` emails;
allows `valid`, `catchall`, `unknown` through. Fails open if NeverBounce
is unavailable.

**How it works:**
- New `src/lib/email-neverbounce.ts` calls the NeverBounce single-check
API with a 5s timeout
- `send()` returns `SendResult` (`{ sent: true } | { sent: false, reason
}`) so callers can react
- Blocked sends and API failures reported to Sentry

**Caller handling:**
- Magic link route returns 400 with "Unable to deliver email to this
address"
- Org invite route expires the invitation row and throws `BAD_REQUEST`
- Billing lifecycle cron removes idempotency row on rejection, allowing
retry next run
- Admin email testing routes through `send()` so NeverBounce is
exercised

## Verification

### Automated tests (50 passing)
- [x] `npx tsc --noEmit` — no type errors
- [x] `pnpm lint` — clean
- [x] `email-neverbounce.test.ts` — 11 tests (all result types,
fail-open, Sentry reporting, timeout, params)
- [x] `magic-link/route.test.ts` — 10 tests
- [x] `autoTopUp.test.ts` — 16 tests
- [x] `email.test.ts` — 13 tests
- [x] Verified via automated agent that this branch preserves all CIO
code intact (10/10 checks pass)

### Manual tests (all passing on localhost)
- [x] **Invalid email blocked**:
`fakeperson@xyznotarealdomainever.comtld` → HTTP 400
- [x] **Disposable email blocked**: `test@mailinator.com` → HTTP 400
- [x] **Valid email allowed**: `support@neverbounce.com` → HTTP 200
- [x] **Real email delivered**: `evan@kilocode.ai` → email received in
inbox
- [x] **Magic link with invalid email**: returns "Unable to deliver"
error
- [x] **Org invite with invalid email**: error returned, invitation
expired
- [x] **NeverBounce not configured (fail-open)**: emails send normally
without API key

## Visual Changes

N/A — no UI changes in this PR (admin page provider selector is
unchanged).

## Reviewer Notes

- `NEVERBOUNCE_API_KEY` has been added to Vercel.
- This PR is independent of the CIO removal PR (#1498) — they can merge
in either order. When combined, they produce the same result as the
original PR #1493.
- NeverBounce API only supports auth via query parameter — no header
auth option available.
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.

2 participants