Fix: send mailto unsubscribe ourselves instead of via the OS handler
The List-Unsubscribe action for mailto: links created a hidden anchor, clicked it and reported success. That hands the mailto: URL to the OS default mail handler - for a webmail user that opens the wrong program or nothing at all, and the unsubscribe message is never sent, while the banner still claims it was. The confirm flow now parses the mailto: URL (address, subject, body - percent-decoded manually since RFC 6068 does not use plus-encoding) and sends the message through the account's own JMAP client, preferring the identity that received the newsletter so the list can match the subscriber. In unified views the send is routed to the email's owning account. Success is only reported once the server accepted the message. The mobile confirm dialog reused the success strings as its question text; it gets proper confirm_message strings in all 22 locales, and success_mailto now says what actually happened.
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
getEmailValidationError,
|
||||
isValidUnsubscribeUrl,
|
||||
parseUnsubscribeUrls,
|
||||
parseMailtoUrl,
|
||||
} from '../validation';
|
||||
|
||||
describe('validation', () => {
|
||||
@@ -359,3 +360,33 @@ describe('validation', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseMailtoUrl', () => {
|
||||
it('parses address, subject and body', () => {
|
||||
const r = parseMailtoUrl('mailto:list@example.com?subject=Unsubscribe%20123&body=Please%20remove');
|
||||
expect(r).toEqual({ to: ['list@example.com'], subject: 'Unsubscribe 123', body: 'Please remove' });
|
||||
});
|
||||
|
||||
it('keeps a literal plus (RFC 6068 uses percent-encoding only)', () => {
|
||||
const r = parseMailtoUrl('mailto:owner+unsub@example.com?subject=a+b');
|
||||
expect(r?.to).toEqual(['owner+unsub@example.com']);
|
||||
expect(r?.subject).toBe('a+b');
|
||||
});
|
||||
|
||||
it('supports multiple recipients and the to param', () => {
|
||||
const r = parseMailtoUrl('mailto:a@example.com,b@example.com?to=c@example.com');
|
||||
expect(r?.to).toEqual(['a@example.com', 'b@example.com', 'c@example.com']);
|
||||
});
|
||||
|
||||
it('returns null without a valid recipient', () => {
|
||||
expect(parseMailtoUrl('mailto:?subject=x')).toBeNull();
|
||||
expect(parseMailtoUrl('mailto:not-an-address')).toBeNull();
|
||||
expect(parseMailtoUrl('https://example.com/unsub')).toBeNull();
|
||||
});
|
||||
|
||||
it('survives malformed percent-encoding', () => {
|
||||
const r = parseMailtoUrl('mailto:list@example.com?subject=%E0%A4%A');
|
||||
expect(r?.to).toEqual(['list@example.com']);
|
||||
expect(r?.subject).toBe('%E0%A4%A');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user