Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <nowiki>
(function () {
    mw.util.addPortletLink(
        'p-tb',
        '#',
        'MassEmail',
        'ca-mass-email'
    );

    document.getElementById('ca-mass-email').addEventListener('click', function (e) {
        e.preventDefault();

        mw.loader.using(['mediawiki.api', 'oojs-ui', 'mediawiki.util'], function () {
            var api = new mw.Api();

            function MassEmailDialog(config) {
                MassEmailDialog.super.call(this, config);
            }
            OO.inheritClass(MassEmailDialog, OO.ui.ProcessDialog);

            MassEmailDialog.static.name = 'massEmailDialog';
            MassEmailDialog.static.title = 'MassEmail';
            MassEmailDialog.static.actions = [
                { action: 'send', label: 'Send', flags: ['primary', 'progressive'] },
                { action: 'cancel', label: 'Cancel', flags: 'safe' }
            ];

            MassEmailDialog.prototype.initialize = function () {
                MassEmailDialog.super.prototype.initialize.apply(this, arguments);

                this.subjectInput = new OO.ui.TextInputWidget({
                    placeholder: 'Enter the subject'
                });

                this.ccMeCheckbox = new OO.ui.CheckboxInputWidget({
                    selected: false
                });

                this.userNamesInput = new OO.ui.TextInputWidget({
                    placeholder: 'Enter usernames (comma-separated, without User: prefix)'
                });

                this.emailContentInput = new OO.ui.MultilineTextInputWidget({
                    placeholder: 'Type your email here',
                    rows: 10
                });

                this.rateLimitNotice = new OO.ui.MessageWidget({
                    type: 'notice',
                    label: new OO.ui.HtmlSnippet(
                        '<strong>Note:</strong> The following rate limits apply:<br>' +
                        '<ul>' +
                        '<li><strong>IP:</strong> 5 emails per day per IP (logged-out and new users)</li>' +
                        '<li><strong>Newbie:</strong> 5 emails per day for non-autoconfirmed users</li>' +
                        '<li><strong>User-Global:</strong> 20 emails per day for users</li>' +
                        '</ul>'
                    )
                });

                this.content = new OO.ui.PanelLayout({
                    padded: true,
                    expanded: false
                });

                this.content.$element.append(
                    new OO.ui.FieldsetLayout({
                        items: [
                            new OO.ui.FieldLayout(this.subjectInput, {
                                label: 'Subject:',
                                align: 'top'
                            }),
                            new OO.ui.FieldLayout(this.ccMeCheckbox, {
                                label: 'CC me',
                                align: 'inline'
                            }),
                            new OO.ui.FieldLayout(this.userNamesInput, {
                                label: 'Usernames:',
                                align: 'top'
                            }),
                            new OO.ui.FieldLayout(this.emailContentInput, {
                                label: 'Email content:',
                                align: 'top'
                            }),
                            new OO.ui.FieldLayout(this.rateLimitNotice, {
                                align: 'top'
                            })
                        ]
                    }).$element
                );

                this.$body.append(this.content.$element);
            };

            MassEmailDialog.prototype.getActionProcess = function (action) {
                var dialog = this;

                if (action === 'send') {
                    var subject = this.subjectInput.getValue();
                    var ccMe = this.ccMeCheckbox.isSelected();
                    var userNames = this.userNamesInput.getValue().split(',').map(function (name) {
                        return name.trim();
                    });
                    var emailContent = this.emailContentInput.getValue();

                    if (!subject || !userNames.length || !emailContent) {
                        return new OO.ui.Process(function () {
                            alert('Please complete all details.');
                        });
                    }

                    var sendEmail = function (userName, token) {
                        var emailParams = {
                            action: 'emailuser',
                            format: 'json',
                            target: userName,
                            subject: subject,
                            text: emailContent,
                            token: token,
                            formatversion: '2'
                        };
                        if (ccMe) {
                            emailParams.ccme = 1;
                        }
                        return api.postWithToken('csrf', emailParams).then(function (response) {
                            if (response.error) {
                                throw new Error(userName + ': ' + response.error.info);
                            }
                            return new Promise(function (resolve) {
                                setTimeout(resolve, 1000);
                            });
                        });
                    };

                    return new OO.ui.Process(function () {
                        api.get({
                            action: 'query',
                            format: 'json',
                            meta: 'tokens',
                            formatversion: '2'
                        }).then(function (data) {
                            var token = data.query.tokens.csrftoken;
                            var failedUsers = [];
                            var successfulUsers = [];

                            userNames.reduce(function (promise, userName) {
                                return promise.then(function () {
                                    return sendEmail(userName, token).then(function () {
                                        successfulUsers.push(userName);
                                    }).catch(function (error) {
                                        failedUsers.push(userName);
                                    });
                                });
                            }, Promise.resolve()).then(function () {
                                dialog.close({ action: action });
                                var alertMessage = 'Emails sent successfully to: ' + successfulUsers.join(', ');
                                if (failedUsers.length > 0) {
                                    alertMessage += '\nFailed to send emails to: ' + failedUsers.join(', ');
                                }
                                alert(alertMessage);
                            }).catch(function (error) {
                                dialog.close({ action: action });
                                alert('An error occurred: ' + error);
                            });
                        });
                    });
                }

                if (action === 'cancel') {
                    return new OO.ui.Process().next(function () {
                        this.close({ action: action });
                    }, this);
                }

                return MassEmailDialog.super.prototype.getActionProcess.call(this, action);
            };

            var windowManager = new OO.ui.WindowManager();
            $(document.body).append(windowManager.$element);
            var dialog = new MassEmailDialog();
            windowManager.addWindows([dialog]);
            windowManager.openWindow(dialog);
        });
    });
})();


// </nowiki>