🎨 Added source attribution info to email alerts (#16360)

refs https://github.com/TryGhost/Team/issues/2489

- adds attribution info for new free and paid members in email alerts
This commit is contained in:
Rishabh Garg 2023-03-06 15:06:47 +05:30 committed by GitHub
parent 74067f23bd
commit d6a1d98aca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 6 deletions

View File

@ -1086,6 +1086,7 @@ module.exports = class MemberRepository {
memberId: member.id,
subscriptionId: subscriptionModel.get('id'),
offerId: offerId,
attribution: data.attribution,
batchId: options.batch_id
});
this.dispatchEvent(activatedEvent, options);

View File

@ -48,6 +48,9 @@
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #394047; font-weight: 400;">{{memberData.email}}</p>
{{/if}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #95A1AD;">Created on {{memberData.createdAt}}{{#if memberData.location}} &#8226; {{memberData.location}} {{/if}}
{{#if referrerSource}}
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #95A1AD;">Source: {{referrerSource}}</p>
{{/if}}
</p>
</td>
</tr>

View File

@ -48,6 +48,9 @@
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #394047; font-weight: 400;">{{memberData.email}}</p>
{{/if}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #95A1AD;">Subscription started on {{subscriptionData.startedOn}} </p>
{{#if referrerSource}}
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #95A1AD;">Source: {{referrerSource}}</p>
{{/if}}
</td>
</tr>
</table>

View File

@ -16,7 +16,9 @@ class StaffServiceEmails {
this.registerPartials();
}
async notifyFreeMemberSignup(member, options) {
async notifyFreeMemberSignup({
member, attribution
}, options) {
const users = await this.models.User.getEmailAlertUsers('free-signup', options);
for (const user of users) {
@ -27,6 +29,7 @@ class StaffServiceEmails {
const templateData = {
memberData,
referrerSource: attribution?.referrerSource,
siteTitle: this.settingsCache.get('title'),
siteUrl: this.urlUtils.getSiteUrl(),
siteDomain: this.siteDomain,
@ -47,7 +50,7 @@ class StaffServiceEmails {
}
}
async notifyPaidSubscriptionStarted({member, subscription, offer, tier}, options = {}) {
async notifyPaidSubscriptionStarted({member, subscription, offer, tier, attribution}, options = {}) {
const users = await this.models.User.getEmailAlertUsers('paid-started', options);
for (const user of users) {
@ -72,6 +75,7 @@ class StaffServiceEmails {
const templateData = {
memberData,
referrerSource: attribution?.referrerSource,
tierData,
offerData,
subscriptionData,

View File

@ -98,13 +98,17 @@ class StaffService {
});
if (type === MemberCreatedEvent && member.status === 'free') {
await this.emails.notifyFreeMemberSignup(member);
await this.emails.notifyFreeMemberSignup({
member,
attribution: event?.data?.attribution
});
} else if (type === SubscriptionActivatedEvent) {
await this.emails.notifyPaidSubscriptionStarted({
member,
offer,
tier,
subscription
subscription,
attribution: event?.data?.attribution
});
} else if (type === SubscriptionCancelledEvent) {
subscription.canceledAt = event.timestamp;

View File

@ -377,7 +377,7 @@ describe('StaffService', function () {
created_at: '2022-08-01T07:30:39.882Z'
};
await service.emails.notifyFreeMemberSignup(member, options);
await service.emails.notifyFreeMemberSignup({member}, options);
mailStub.calledOnce.should.be.true();
testCommonMailData(stubs);
@ -402,7 +402,7 @@ describe('StaffService', function () {
created_at: '2022-08-01T07:30:39.882Z'
};
await service.emails.notifyFreeMemberSignup(member, options);
await service.emails.notifyFreeMemberSignup({member}, options);
mailStub.calledOnce.should.be.true();
testCommonMailData(stubs);
@ -418,6 +418,39 @@ describe('StaffService', function () {
sinon.match.has('html', sinon.match('Created on 1 Aug 2022 &#8226; France'))
).should.be.true();
});
it('sends free member signup alert with attribution', async function () {
const member = {
name: 'Ghost',
email: 'member@example.com',
id: 'abc',
geolocation: '{"country": "France"}',
created_at: '2022-08-01T07:30:39.882Z'
};
const attribution = {
referrerSource: 'Twitter'
};
await service.emails.notifyFreeMemberSignup({member, attribution}, options);
mailStub.calledOnce.should.be.true();
testCommonMailData(stubs);
getEmailAlertUsersStub.calledWith('free-signup').should.be.true();
mailStub.calledWith(
sinon.match({subject: '🥳 Free member signup: Ghost'})
).should.be.true();
mailStub.calledWith(
sinon.match.has('html', sinon.match('🥳 Free member signup: Ghost'))
).should.be.true();
mailStub.calledWith(
sinon.match.has('html', sinon.match('Created on 1 Aug 2022 &#8226; France'))
).should.be.true();
mailStub.calledWith(
sinon.match.has('html', sinon.match('Source: Twitter'))
).should.be.true();
});
});
describe('notifyPaidSubscriptionStart', function () {
@ -451,6 +484,21 @@ describe('StaffService', function () {
};
});
it('sends paid subscription start alert with attribution', async function () {
const attribution = {
referrerSource: 'Twitter'
};
await service.emails.notifyPaidSubscriptionStarted({member, offer: null, tier, subscription, attribution}, options);
mailStub.calledOnce.should.be.true();
testCommonPaidSubMailData({...stubs, member});
// check attribution text
mailStub.calledWith(
sinon.match.has('html', sinon.match('Source: Twitter'))
).should.be.true();
});
it('sends paid subscription start alert without offer', async function () {
await service.emails.notifyPaidSubscriptionStarted({member, offer: null, tier, subscription}, options);