Merge pull request #30002 from overleaf/ii-domain-capture-send-email-to-admin

[web] Send member limit notification email to group admin

GitOrigin-RevId: 520fef0dd3eab52bfb766f6f116cb4b8533b4cae
This commit is contained in:
ilkin-overleaf
2025-12-08 09:05:53 +00:00
committed by Copybot
parent 3f0dc71d6c
commit f959329337
3 changed files with 97 additions and 0 deletions
@@ -1023,6 +1023,36 @@ templates.taxExemptCertificateRequired = NoCTAEmailTemplate({
},
})
templates.groupMemberLimitWarning = ctaTemplate({
subject(opts) {
return `${opts.groupName || 'Your group'} is approaching its member limit`
},
title(opts) {
return `${opts.groupName || 'Your group'} is approaching its member limit`
},
greeting(opts) {
return opts.firstName ? `Hi ${opts.firstName},` : 'Hi,'
},
message(opts) {
return [
`Your group "${opts.groupName}" is approaching its member limit.`,
`<b>Current usage:</b> ${opts.currentMembers} of ${opts.membersLimit} licenses used (${opts.remainingSeats} remaining)`,
'With domain capture enabled, users with verified email addresses from your domain can automatically join the group via SSO. Once the member limit is reached, new users will be blocked from joining.',
]
},
secondaryMessage() {
return [
'To ensure uninterrupted access for your users, consider adding more licenses or removing inactive members.',
]
},
ctaText() {
return 'Add licenses'
},
ctaURL() {
return `${settings.siteUrl}/user/subscription/group/add-users`
},
})
function _formatUserNameAndEmail(user, placeholder) {
if (user.first_name && user.last_name) {
const fullName = `${user.first_name} ${user.last_name}`
@@ -39,6 +39,7 @@ export const SubscriptionSchema = new Schema(
domainCaptureEnabled: { type: Boolean, default: false },
managedUsersEnabled: { type: Boolean, default: false },
membersLimit: { type: Number, default: 0 },
membersLimitNotificationSent: { type: Boolean, default: false },
customAccount: Boolean,
features: {
managedUsers: { type: Boolean, default: true },
@@ -976,6 +976,72 @@ describe('EmailBuilder', function () {
})
})
})
describe('groupMemberLimitWarning', function () {
beforeEach(function (ctx) {
ctx.emailAddress = 'example@overleaf.com'
ctx.opts = {
to: ctx.emailAddress,
groupName: 'Example Group',
firstName: 'Joe',
currentMembers: 9,
membersLimit: 10,
remainingSeats: 1,
}
ctx.email = ctx.EmailBuilder.buildEmail(
'groupMemberLimitWarning',
ctx.opts
)
ctx.expectedUrl = `${ctx.settings.siteUrl}/user/subscription/group/add-users`
})
it('should build the email', function (ctx) {
expect(ctx.email.html).to.exist
expect(ctx.email.text).to.exist
})
describe('HTML email', function () {
it('should include a CTA button and a fallback CTA link', function (ctx) {
const dom = cheerio.load(ctx.email.html)
const plainText = dom.text()
expect(ctx.email.subject).to.equal(
`${ctx.opts.groupName} is approaching its member limit`
)
expect(ctx.email.html).to.exist
expect(ctx.email.html).to.contain(
`Your group "${ctx.opts.groupName}" is approaching its member limit.`
)
expect(plainText).to.contain(
`Current usage: ${ctx.opts.currentMembers} of ` +
`${ctx.opts.membersLimit} licenses used ` +
`(${ctx.opts.remainingSeats} remaining)`
)
expect(ctx.email.html).to.contain(
'With domain capture enabled, users with verified email ' +
'addresses from your domain can automatically join the group ' +
'via SSO. Once the member limit is reached, new users will ' +
'be blocked from joining.'
)
expect(ctx.email.html).to.contain(
'To ensure uninterrupted access for your users, consider ' +
'adding more licenses or removing inactive members.'
)
const buttonLink = dom('td a')
expect(buttonLink).to.exist
expect(buttonLink.attr('href')).to.equal(ctx.expectedUrl)
const fallback = dom('.force-overleaf-style').last()
expect(fallback).to.exist
const fallbackLink = fallback.html().replace(/&amp;/g, '&')
expect(fallbackLink).to.contain(ctx.expectedUrl)
})
})
describe('plain text email', function () {
it('should contain the CTA link', function (ctx) {
expect(ctx.email.text).to.contain(ctx.expectedUrl)
})
})
})
})
})
})