Skip to content

Commit 76281f5

Browse files
committed
🐛 Fixed from address for gmail inbox links (#26312)
ref https://linear.app/ghost/issue/NY-1012/ Inbox links used the support address (reply) instead of the specified from address. Gmail allows us to pass params to automatically filter to a message, and in this case we're filtering to the wrong one, making it look like the email never got sent.
1 parent 4a159a4 commit 76281f5

6 files changed

Lines changed: 44 additions & 9 deletions

File tree

ghost/core/core/server/services/email-address/email-address-service-wrapper.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class EmailAddressServiceWrapper {
3333
getFallbackEmail: () => {
3434
return config.get('hostSettings:managedEmail:fallbackAddress') || null;
3535
},
36+
getMembersSupportAddress: () => {
37+
return settingsHelpers.getMembersSupportAddress();
38+
},
3639
isValidEmailAddress: (emailAddress) => {
3740
return validator.isEmail(emailAddress);
3841
}

ghost/core/core/server/services/email-address/email-address-service.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class EmailAddressService {
2424
#getDefaultEmail: () => EmailAddress;
2525
#getFallbackDomain: () => string | null;
2626
#getFallbackEmail: () => EmailAddress | null;
27+
#getMembersSupportAddress: () => string;
2728
#isValidEmailAddress: (email: string) => boolean;
2829

2930
constructor(dependencies: {
@@ -32,12 +33,14 @@ export class EmailAddressService {
3233
getFallbackDomain: () => string | null,
3334
getDefaultEmail: () => EmailAddress,
3435
getFallbackEmail: () => string | null,
36+
getMembersSupportAddress: () => string,
3537
isValidEmailAddress: (email: string) => boolean
3638
}) {
3739
this.#getManagedEmailEnabled = dependencies.getManagedEmailEnabled;
3840
this.#getSendingDomain = dependencies.getSendingDomain;
3941
this.#getFallbackDomain = dependencies.getFallbackDomain;
4042
this.#getDefaultEmail = dependencies.getDefaultEmail;
43+
this.#getMembersSupportAddress = dependencies.getMembersSupportAddress;
4144
this.#getFallbackEmail = () => {
4245
const fallbackAddress = dependencies.getFallbackEmail();
4346
if (!fallbackAddress) {
@@ -68,6 +71,16 @@ export class EmailAddressService {
6871
return this.#getFallbackEmail();
6972
}
7073

74+
/**
75+
* Get the actual sender address for member emails after DMARC transformation.
76+
* On managed platforms, this may differ from the configured support address to ensure DMARC compliance.
77+
*/
78+
getMembersSupportAddress(): string {
79+
const configuredAddress = this.#getMembersSupportAddress();
80+
const transformed = this.getAddressFromString(configuredAddress);
81+
return transformed.from.address;
82+
}
83+
7184
getAddressFromString(from: string, replyTo?: string): EmailAddresses {
7285
const parsedFrom = EmailAddressParser.parse(from);
7386
const parsedReplyTo = replyTo ? EmailAddressParser.parse(replyTo) : undefined;

ghost/core/core/server/services/members/api.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const newslettersService = require('../newsletters');
1919
const memberAttributionService = require('../member-attribution');
2020
const emailSuppressionList = require('../email-suppression-list');
2121
const commentsService = require('../comments');
22+
const emailAddressService = require('../email-address');
2223
const {t} = require('../i18n');
2324
const sentry = require('../../../shared/sentry');
2425

@@ -254,7 +255,8 @@ function createApiInstance(config) {
254255
sentry,
255256
settingsHelpers,
256257
urlUtils,
257-
commentsService
258+
commentsService,
259+
emailAddressService: emailAddressService.service
258260
});
259261

260262
return membersApiInstance;

ghost/core/core/server/services/members/members-api/controllers/router-controller.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ module.exports = class RouterController {
7575
* @param {any} deps.settingsCache
7676
* @param {any} deps.settingsHelpers
7777
* @param {any} deps.urlUtils
78+
* @param {any} deps.emailAddressService
7879
*/
7980
constructor({
8081
offersAPI,
@@ -93,7 +94,8 @@ module.exports = class RouterController {
9394
sentry,
9495
settingsCache,
9596
settingsHelpers,
96-
urlUtils
97+
urlUtils,
98+
emailAddressService
9799
}) {
98100
this._offersAPI = offersAPI;
99101
this._paymentsService = paymentsService;
@@ -112,6 +114,7 @@ module.exports = class RouterController {
112114
this._settingsCache = settingsCache;
113115
this._settingsHelpers = settingsHelpers;
114116
this._urlUtils = urlUtils;
117+
this._emailAddressService = emailAddressService;
115118
}
116119

117120
async ensureStripe(_req, res, next) {
@@ -733,7 +736,7 @@ module.exports = class RouterController {
733736

734737
const inboxLinks = await getInboxLinks({
735738
recipient: normalizedEmail,
736-
sender: this._settingsHelpers.getMembersSupportAddress(),
739+
sender: this._emailAddressService.getMembersSupportAddress(),
737740
dnsResolver: this.#inboxLinksDnsResolver
738741
});
739742
if (inboxLinks) {

ghost/core/core/server/services/members/members-api/members-api.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ module.exports = function MembersAPI({
7979
sentry,
8080
settingsHelpers,
8181
urlUtils,
82-
commentsService
82+
commentsService,
83+
emailAddressService
8384
}) {
8485
const tokenService = new TokenService({
8586
privateKey,
@@ -213,7 +214,8 @@ module.exports = function MembersAPI({
213214
settingsCache,
214215
settingsHelpers,
215216
sentry,
216-
urlUtils
217+
urlUtils,
218+
emailAddressService
217219
});
218220

219221
const wellKnownController = new WellKnownController({

ghost/core/test/unit/server/services/members/members-api/controllers/router-controller.test.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@ describe('RouterController', function () {
1616
let getDonationLinkSpy;
1717
let settingsCache;
1818
let settingsHelpers;
19+
let emailAddressService;
1920

2021
beforeEach(async function () {
22+
// Mock emailAddressService for inbox links sender address transformation
23+
emailAddressService = {
24+
getMembersSupportAddress: sinon.stub().returns('noreply@example.com')
25+
};
26+
2127
getPaymentLinkSpy = sinon.spy();
2228
getDonationLinkSpy = sinon.spy();
2329
tiersService = {
@@ -90,7 +96,8 @@ describe('RouterController', function () {
9096
stripeAPIService,
9197
labsService,
9298
settingsCache,
93-
settingsHelpers
99+
settingsHelpers,
100+
emailAddressService
94101
});
95102

96103
await routerController.createCheckoutSession({
@@ -126,7 +133,8 @@ describe('RouterController', function () {
126133
labsService,
127134
settingsCache,
128135
settingsHelpers,
129-
newslettersService: newslettersServiceStub
136+
newslettersService: newslettersServiceStub,
137+
emailAddressService
130138
});
131139
const newsletters = [
132140
{id: 'abc123', name: 'Newsletter 1'},
@@ -693,6 +701,7 @@ describe('RouterController', function () {
693701
sendEmailWithMagicLink: sendEmailWithMagicLinkStub,
694702
settingsCache,
695703
settingsHelpers,
704+
emailAddressService,
696705
...deps
697706
});
698707
};
@@ -848,6 +857,7 @@ describe('RouterController', function () {
848857
sendEmailWithMagicLink: sendEmailWithMagicLinkStub,
849858
settingsCache,
850859
settingsHelpers,
860+
emailAddressService,
851861
...deps
852862
});
853863
};
@@ -923,7 +933,8 @@ describe('RouterController', function () {
923933
settingsHelpers,
924934
newslettersService: {},
925935
sentry: {},
926-
urlUtils: {}
936+
urlUtils: {},
937+
emailAddressService
927938
});
928939

929940
routerController._handleSignin = handleSigninStub;
@@ -1035,7 +1046,8 @@ describe('RouterController', function () {
10351046
stripeAPIService,
10361047
labsService,
10371048
settingsCache,
1038-
newslettersService: newslettersServiceStub
1049+
newslettersService: newslettersServiceStub,
1050+
emailAddressService
10391051
});
10401052
});
10411053

0 commit comments

Comments
 (0)