Skip to content

improvement(emails): funnel every sender through the shared render and subject layer - #6482

Merged
waleedlatif1 merged 3 commits into
stagingfrom
improvement/email-shared-layer
Aug 10, 2026
Merged

improvement(emails): funnel every sender through the shared render and subject layer#6482
waleedlatif1 merged 3 commits into
stagingfrom
improvement/email-shared-layer

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Follow-up to #6479. That PR made the shared layer correct; this one makes every sender actually use it.

What was bypassing it

Auditing all 19 sendEmail call sites turned up three real bypasses:

  • invoices.ts imported the PaymentFailedEmail component and called render() itself instead of using renderPaymentFailedEmail, and hardcoded 'Payment Failed - Action Required' — the only subject in the system that wasn't brand-aware, because there was no payment-failed case in EmailSubjectType.
  • subscription.ts built its subject inline with ${(await import('@/ee/whitelabeling')).getBrandConfig().name} embedded in the template literal, and called getDisplayPlanName twice. It also imported getEmailSubject and never used it.
  • lib/mothership/inbox/response.ts carried an entire second email system — its own InboxResponseEmail component and ~150 lines of style literals (#ededed, #2563eb, #f5f5f5), none of it on the shared tokens. Moved to components/emails/agent/inbox-response-email.tsx behind renderInboxResponseEmail/renderInboxErrorEmail; the file drops from 317 to 80 lines.

Subjects now live in subjects.ts — added a payment-failed case plus getPlanWelcomeSubject, getRequestConfirmationSubject, and getOtpSubject alongside the existing getLimitEmailSubject. Removed three EmailSubjectType cases that nothing called.

Two user-facing copy fixes

  • The agent's plain-text reply signed off "Best, Mothership" while the HTML said "Sim" — both a constitution violation and an inconsistency inside the same message. Both now use getBrandConfig().name.
  • The payment-failed subject is now Payment failed on Sim — action required (was brand-free). Its preview text had also drifted to different casing; both now read from one source.

Guard

components/emails/boundary.test.ts fails the build if a sender imports @react-email/render, imports a template component instead of its wrapper, or builds its own subject. It scans only files that actually call sendEmail, and exempts bracket-tagged internal team-inbox subjects. Verified it fails on each violation.

Type of Change

  • Improvement

Testing

850 tests pass across components/emails, lib/billing, lib/mothership, lib/messaging, lib/invitations, lib/workflows/schedules. Type-check and lint clean.

Rendered all 23 templates and scanned for #dedede, #ededed, #707070, #f8f8f8, #2563eb, border-radius:6px, font-weight:700|bold, text-transform:uppercase, and the literal Mothership — 0 hits.

Two review agents audited this for regressions and over-engineering. Fixes applied from that pass: restored Roboto/Helvetica Neue to the system font stack (tokenizing had silently dropped them, affecting Android/Linux), removed a dead sentDate prop, replaced brittle regex parsing in base.tokens.test.ts with a direct import of the Tailwind config (md was matching borderRadius.md), narrowed the boundary scan to real senders, and deleted a test that only asserted its own fixture data.

One deliberate visual change to call out: agent-reply links were #2563eb blue and are now neutral with a dashed underline, matching baseStyles.link. That is the platform treatment, but it is a visible change to a live email.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 10, 2026 2:23am

Request Review

@cursor

cursor Bot commented Aug 10, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches transactional billing emails, OTP flows, and live agent inbox HTML; boundary tests reduce regression risk but link styling and copy changes are user-visible.

Overview
Routes all product email senders through render.ts wrappers and subjects.ts helpers instead of calling @react-email/render, importing template components directly, or hardcoding subject strings.

Billing and inbox bypasses removed: invoices.ts now uses renderPaymentFailedEmail and getEmailSubject('payment-failed'); subscription.ts uses getPlanWelcomeSubject; OTP/contact/help routes use getOtpSubject / getRequestConfirmationSubject. Agent inbox replies move from ~150 lines of inline styles in lib/mothership/inbox/response.ts to InboxResponseEmail / renderInboxResponseEmail / renderInboxErrorEmail, with plain-text and HTML signatures both using getBrandConfig().name.

Shared tokens and guards: Email base styles add textSecondary and small font size; token tests import Tailwind config directly. boundary.test.ts fails the build if a sendEmail caller bypasses the shared layer.

User-visible tweaks: Payment-failed subject is brand-aware; agent reply links shift from blue to neutral dashed underline (platform link style).

Reviewed by Cursor Bugbot for commit 42d1ba7. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR centralizes remaining email rendering and subject generation behind the shared email layer and adds a build-time boundary guard.

  • Migrates OTP, request-confirmation, subscription, invoice, and inbox-response senders to shared render and subject helpers.
  • Adds shared inbox response/error templates and brand-aware subject helpers.
  • Adds tests enforcing shared rendering, subject generation, and design-token consistency.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/components/emails/boundary.test.ts Adds sender discovery and guards against direct renderer access, template-component usage, and inline product subjects; the previously reported dynamic-import hole is addressed.
apps/sim/components/emails/render.ts Adds shared wrappers for inbox response/error rendering and sanitizes unsafe URL schemes.
apps/sim/components/emails/subjects.ts Centralizes additional brand-aware payment, plan, request-confirmation, and OTP subjects.
apps/sim/components/emails/agent/inbox-response-email.tsx Moves inbox reply and error markup into the shared email component layer.
apps/sim/lib/mothership/inbox/response.ts Replaces the local email implementation with shared rendering and subject helpers.
apps/sim/lib/billing/webhooks/invoices.ts Routes payment-failure emails through the shared renderer and brand-aware subject helper.
apps/sim/lib/billing/core/subscription.ts Routes subscription welcome subjects through the shared subject layer and avoids redundant plan-name resolution.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Sender[Email sender] --> Subject[Shared subject helpers]
  Sender --> Render[Shared render wrappers]
  Subject --> Mailer[sendEmail]
  Render --> Template[Shared email templates]
  Template --> Mailer
  Guard[Boundary tests] -. validates .-> Sender
  Guard -. validates .-> Render
Loading

Reviews (3): Last reviewed commit: "fix(emails): mock the module the limit-n..." | Re-trigger Greptile

Comment thread apps/sim/components/emails/boundary.test.ts Outdated
Comment thread apps/sim/app/api/files/public/[token]/otp/route.ts
Comment thread apps/sim/lib/billing/core/limit-notifications.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

1 issue from previous review remains unresolved.

Fix All in Cursor

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 5d9388e. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 42d1ba7. Configure here.

@waleedlatif1
waleedlatif1 merged commit a72427f into staging Aug 10, 2026
30 checks passed
@waleedlatif1
waleedlatif1 deleted the improvement/email-shared-layer branch August 10, 2026 02:28
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