Skip to content

Commit de04785

Browse files
committed
fix(email): address Greptile review feedback
- add a sendEmail assertion locking in the payment-failure email's replyTo - clarify getPersonalEmailFrom() JSDoc so it doesn't overstate replyTo's scope
1 parent 6c5e70f commit de04785

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

apps/sim/lib/billing/webhooks/invoices.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ import {
110110
handleInvoicePaymentSucceeded,
111111
resetUsageForSubscription,
112112
} from '@/lib/billing/webhooks/invoices'
113+
import { sendEmail } from '@/lib/messaging/email/mailer'
113114

114115
interface SelectResponse {
115116
limitResult?: unknown
@@ -195,6 +196,49 @@ describe('invoice billing recovery', () => {
195196
expect(mockUnblockOrgMembers).not.toHaveBeenCalled()
196197
})
197198

199+
it('sends the payment-failure email with the shared help inbox as reply-to', async () => {
200+
queueSelectResponse({
201+
limitResult: [
202+
{
203+
id: 'sub-db-1',
204+
plan: 'team_8000',
205+
referenceId: 'org-1',
206+
stripeSubscriptionId: 'sub_stripe_1',
207+
},
208+
],
209+
})
210+
queueSelectResponse({
211+
whereResult: [{ userId: 'owner-1', role: 'owner' }],
212+
})
213+
queueSelectResponse({
214+
whereResult: [{ email: 'owner@sim.test', name: 'Owner' }],
215+
})
216+
217+
await handleInvoicePaymentFailed(
218+
createInvoiceEvent('invoice.payment_failed', {
219+
amount_due: 3582,
220+
attempt_count: 1,
221+
customer: 'cus_123',
222+
customer_email: 'owner@sim.test',
223+
hosted_invoice_url: 'https://stripe.test/invoices/in_123',
224+
id: 'in_123',
225+
metadata: {
226+
billingPeriod: '2026-04',
227+
subscriptionId: 'sub_stripe_1',
228+
type: 'overage_threshold_billing_org',
229+
},
230+
})
231+
)
232+
233+
expect(sendEmail).toHaveBeenCalledWith(
234+
expect.objectContaining({
235+
to: 'owner@sim.test',
236+
from: 'billing@sim.test',
237+
replyTo: 'help@sim.test',
238+
})
239+
)
240+
})
241+
198242
it('unblocks org members when the matching metadata-backed invoice payment succeeds', async () => {
199243
queueSelectResponse({
200244
limitResult: [

apps/sim/lib/messaging/email/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ export function extractEmailFromAddress(fromAddress: string): string | undefined
3535
}
3636

3737
/**
38-
* Get the personal email from address, for the "from" header only.
39-
* Reply-to for these emails should come from `getHelpEmailAddress()`, not this value.
38+
* Get the personal email from address and reply-to. Lifecycle and billing notification
39+
* emails should use `getHelpEmailAddress()` for reply-to instead of the value returned here.
4040
*/
4141
export function getPersonalEmailFrom(): { from: string; replyTo: string | undefined } {
4242
const personalFrom = env.PERSONAL_EMAIL_FROM

0 commit comments

Comments
 (0)