Skip to content

Comments

feat: Add email template for failed team subscription payments#24518

Draft
joeauyeung wants to merge 130 commits intomainfrom
devin/team-subscription-payment-failed-email-1760634153
Draft

feat: Add email template for failed team subscription payments#24518
joeauyeung wants to merge 130 commits intomainfrom
devin/team-subscription-payment-failed-email-1760634153

Conversation

@joeauyeung
Copy link
Contributor

@joeauyeung joeauyeung commented Oct 16, 2025

Stacked on #24803

What does this PR do?

Implements email notification infrastructure for failed subscription payments on team/organization accounts. Creates a generic email template and adds a method to TeamBillingService that automatically generates Stripe billing portal URLs and sends notifications to admins.

Key changes:

  • Generic subscription payment failed email template (works for both teams and orgs)
  • TeamBillingService.sendPaymentFailedEmails() method with automatic billing portal URL generation
  • BillingPortalService.processBillingPortalWithoutPermissionChecks() for system-triggered emails
  • PermissionCheckService.getUsersWithPermissionForTeam() to find users with billing permissions
  • Translation strings for email content
  • Updated TeamBillingInput to include name field

⚠️ Important Notes:

  • This PR provides the email infrastructure only - actual Stripe webhook integration is NOT included
  • The sendPaymentFailedEmails() method needs to be called from webhook handlers separately

https://www.loom.com/share/c121e89183a9422f9377a61b7a43d82e


Requested by: joe@cal.com (@joeauyeung)
Devin session: https://app.devin.ai/sessions/d8234d8242e74f2eb84a59a412ff847e

Updates since last revision

  • Resolved merge conflicts with main branch (translation strings, imports)
  • Fixed failing TeamRepository.test.ts unit test - added name: true to expected select to match implementation

Human Review Checklist

Critical items to verify:

  1. Type safety concerns:

    • Verify TFunction type from next-i18next matches the translation function actually used in the codebase
    • Confirm no existing code breaks from adding name to TeamBillingInput (search for places that construct this type)
  2. Integration correctness:

    • Validate billing portal return URL (${WEBAPP_URL}/settings/teams/${teamId}/billing) is appropriate
    • Check if error throwing (vs just logging) is correct for this use case
    • Verify translation key naming follows existing conventions
  3. Security considerations:

    • Review processBillingPortalWithoutPermissionChecks - intentionally bypasses permissions for system emails, ensure it's not exposed inappropriately
  4. Testing requirements:

    • Manual test: Create a team with subscription, call sendPaymentFailedEmails(), verify email content and billing portal link work

How should this be tested?

Prerequisites:

  • Stripe API keys configured (STRIPE_PRIVATE_KEY)
  • Test team/org with active subscription in metadata
  • Email service configured

Manual test:

import { TeamBillingService } from "@calcom/features/ee/billing/service/teams/TeamBillingService";
import { prisma } from "@calcom/prisma";

// 1. Get a test team with subscription
const team = await prisma.team.findFirst({
  where: { metadata: { path: ["subscriptionId"], not: null } },
  select: { id: true, name: true, parentId: true, metadata: true, isOrganization: true }
});

// 2. Create billing service and send test email
const billingService = new TeamBillingService(team, billingRepository);
await billingService.sendPaymentFailedEmails();

// 3. Verify:
// - Email received by team admins with correct team name
// - Billing portal link works and redirects to Stripe
// - Email translations are correct

Mandatory Tasks

  • I have self-reviewed the code
  • N/A - No documentation changes needed (email infrastructure, not user-facing feature)
  • I confirm automated tests are in place - ⚠️ Manual testing required for email flow, unit tests updated

Checklist

  • I have read the contributing guide
  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have checked if my changes generate no new warnings

- Created TeamSubscriptionPaymentFailedEmail React component
- Added sendTeamSubscriptionPaymentFailedEmail service method
- Added translation strings for email content
- Includes billing portal link and support contact info

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@keithwillcode keithwillcode added core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO labels Oct 16, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 16, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/team-subscription-payment-failed-email-1760634153

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…d orgs

- Renamed files from team-subscription to subscription
- Changed 'teamName' to 'entityName' to support both teams and organizations
- Updated translation keys to be more generic (subscription_payment_failed_*)
- Updated email manager export to sendSubscriptionPaymentFailedEmail

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
@vercel
Copy link

vercel bot commented Oct 16, 2025

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

Project Deployment Preview Comments Updated (UTC)
cal-companion Error Error Dec 1, 2025 3:31pm
2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Dec 1, 2025 3:31pm
cal-eu Ignored Ignored Dec 1, 2025 3:31pm

devin-ai-integration bot and others added 3 commits October 16, 2025 17:16
- Added sendPaymentFailedEmail() method to InternalTeamBilling class
- Retrieves subscription from Stripe to get customer ID
- Generates Stripe billing portal URL automatically
- Sends email using sendSubscriptionPaymentFailedEmail service
- Updated TeamBillingInput to include 'name' field for email content

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
- Updated /api/email route to support multiple email templates via query parameter
- Added SubscriptionPaymentFailedEmail preview with customizable entityName and billingPortalUrl
- Removed standalone email-preview endpoint in favor of unified /api/email route

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Base automatically changed from implement-team-billing-repository-factor to main November 19, 2025 15:21
@github-actions
Copy link
Contributor

Devin AI is resolving merge conflicts

This PR has merge conflicts with the main branch. A Devin session has been created to automatically resolve them.

View Devin Session

Devin will:

  1. Merge the latest main into this branch
  2. Resolve any conflicts intelligently
  3. Run lint/type checks to ensure validity
  4. Push the resolved changes

If you prefer to resolve conflicts manually, you can close the Devin session and handle it yourself.

…760634153

Resolved merge conflicts in:
- packages/features/pbac/infrastructure/repositories/__tests__/PermissionRepository.integration-test.ts
  (kept both getUsersWithPermissionInTeam and getTeamIdsWithPermissions test suites)

Fixed type errors:
- Added missing import for PermissionCheckService in TeamBillingService.ts
- Added 'name' property to TeamBillingInput type in ITeamBillingService.ts

Co-Authored-By: unknown <>
@vercel vercel bot temporarily deployed to Preview – cal-companion January 13, 2026 08:46 Inactive
@vercel vercel bot temporarily deployed to Preview – dev January 13, 2026 08:46 Inactive
@joeauyeung joeauyeung marked this pull request as ready for review January 27, 2026 14:51
@joeauyeung joeauyeung marked this pull request as draft January 27, 2026 14:52
Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 15 files

@github-actions
Copy link
Contributor

github-actions bot commented Jan 27, 2026

E2E results are ready!

devin-ai-integration bot and others added 5 commits January 27, 2026 15:19
…issionInTeam

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
… changes

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
…issionInTeam

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO ready-for-e2e size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants