[BREAK][IMPROVE] New emails design (#12009)

pull/12175/head^2
Guilherme Gazzo 7 years ago committed by Diego Sampaio
parent cabcecab88
commit 335d9d8ef2
  1. 1
      .meteor/packages
  2. 1
      .meteor/versions
  3. 6245
      package-lock.json
  4. 1
      package.json
  5. 31
      packages/rocketchat-channel-settings-mail-messages/server/methods/mailMessages.js
  6. 21
      packages/rocketchat-i18n/i18n/en.i18n.json
  7. 2
      packages/rocketchat-katex/package-lock.json
  8. 2
      packages/rocketchat-lib/package.js
  9. 82
      packages/rocketchat-lib/server/functions/notifications/email.js
  10. 179
      packages/rocketchat-lib/server/functions/saveUser.js
  11. 51
      packages/rocketchat-lib/server/methods/sendInvitationEmail.js
  12. 10
      packages/rocketchat-lib/server/methods/sendSMTPTestEmail.js
  13. 425
      packages/rocketchat-lib/server/startup/email.js
  14. 417
      packages/rocketchat-lib/server/startup/settings.js
  15. 3
      packages/rocketchat-lib/startup/index.js
  16. 21
      packages/rocketchat-livechat/server/lib/Livechat.js
  17. 0
      packages/rocketchat-mail-messages/client/router.js
  18. 0
      packages/rocketchat-mail-messages/client/startup.js
  19. 0
      packages/rocketchat-mail-messages/client/views/mailer.html
  20. 0
      packages/rocketchat-mail-messages/client/views/mailer.js
  21. 0
      packages/rocketchat-mail-messages/client/views/mailerUnsubscribe.html
  22. 0
      packages/rocketchat-mail-messages/client/views/mailerUnsubscribe.js
  23. 0
      packages/rocketchat-mail-messages/lib/Mailer.js
  24. 39
      packages/rocketchat-mail-messages/package.js
  25. 61
      packages/rocketchat-mail-messages/server/functions/sendMail.js
  26. 0
      packages/rocketchat-mail-messages/server/functions/unsubscribe.js
  27. 0
      packages/rocketchat-mail-messages/server/methods/sendMail.js
  28. 0
      packages/rocketchat-mail-messages/server/methods/unsubscribe.js
  29. 0
      packages/rocketchat-mail-messages/server/models/Users.js
  30. 0
      packages/rocketchat-mail-messages/server/startup.js
  31. 27
      packages/rocketchat-mailer/package.js
  32. 107
      packages/rocketchat-mailer/server/api.js
  33. 82
      packages/rocketchat-mailer/server/functions/sendMail.js
  34. 26
      packages/rocketchat-smarsh-connector/server/functions/sendEmail.js
  35. 50
      packages/rocketchat-user-data-download/server/cronProcessDownloads.js
  36. 63
      server/lib/accounts.js
  37. 19
      server/methods/registerUser.js
  38. 65
      server/methods/sendConfirmationEmail.js
  39. 84
      server/methods/sendForgotPasswordEmail.js
  40. 49
      server/methods/setUserActiveStatus.js
  41. 66
      server/startup/migrations/v134.js

@ -202,3 +202,4 @@ rocketchat:lazy-load
tap:i18n
underscore
rocketchat:bigbluebutton
rocketchat:mailmessages

@ -184,6 +184,7 @@ rocketchat:livestream@0.0.5
rocketchat:logger@0.0.1
rocketchat:login-token@1.0.0
rocketchat:mailer@0.0.1
rocketchat:mailmessages@0.0.1
rocketchat:mapview@0.0.1
rocketchat:markdown@0.0.2
rocketchat:mentions@0.0.1

6245
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -154,6 +154,7 @@
"ip-range-check": "^0.0.2",
"jquery": "^3.3.1",
"jschardet": "^1.6.0",
"juice": "^4.3.2",
"ldapjs": "^1.0.2",
"less": "https://github.com/meteor/less.js/tarball/8130849eb3d7f0ecf0ca8d0af7c4207b0442e3f6",
"less-plugin-autoprefix": "^1.5.1",

@ -1,9 +1,11 @@
import _ from 'underscore';
import moment from 'moment';
import * as Mailer from 'meteor/rocketchat:mailer';
Meteor.methods({
'mailMessages'(data) {
if (!Meteor.userId()) {
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'mailMessages',
});
@ -16,19 +18,19 @@ Meteor.methods({
messages: [String],
language: String,
}));
const room = Meteor.call('canAccessRoom', data.rid, Meteor.userId());
const room = Meteor.call('canAccessRoom', data.rid, userId);
if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', {
method: 'mailMessages',
});
}
if (!RocketChat.authz.hasPermission(Meteor.userId(), 'mail-messages')) {
if (!RocketChat.authz.hasPermission(userId, 'mail-messages')) {
throw new Meteor.Error('error-action-not-allowed', 'Mailing is not allowed', {
method: 'mailMessages',
action: 'Mailing',
});
}
const rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/;
const emails = _.compact(data.to_emails.trim().split(','));
const missing = [];
if (data.to_users.length > 0) {
@ -41,9 +43,8 @@ Meteor.methods({
}
});
}
console.log('Sending messages to e-mails: ', emails);
_.each(emails, (email) => {
if (!rfcMailPatternWithName.test(email.trim())) {
if (!Mailer.checkAddressFormat(email.trim())) {
throw new Meteor.Error('error-invalid-email', `Invalid email ${ email }`, {
method: 'mailMessages',
email,
@ -61,8 +62,6 @@ Meteor.methods({
}
}
const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
const html = RocketChat.models.Messages.findByRoomIdAndMessageIds(data.rid, data.messages, {
sort: { ts: 1 },
}).map(function(message) {
@ -70,16 +69,14 @@ Meteor.methods({
return `<p style='margin-bottom: 5px'><b>${ message.u.username }</b> <span style='color: #aaa; font-size: 12px'>${ dateTime }</span><br/>${ RocketChat.Message.parse(message, data.language) }</p>`;
}).join('');
Meteor.defer(function() {
Email.send({
to: emails,
from: RocketChat.settings.get('From_Email'),
replyTo: email,
subject: data.subject,
html: header + html + footer,
});
return console.log(`Sending email to ${ emails.join(', ') }`);
Mailer.send({
to: emails,
from: RocketChat.settings.get('From_Email'),
replyTo: email,
subject: data.subject,
html,
});
return {
success: true,
missing,

@ -65,9 +65,7 @@
"Accounts_EmailVerification": "Email Verification",
"Accounts_EmailVerification_Description": "Make sure you have correct SMTP settings to use this feature",
"Accounts_Enrollment_Email": "Enrollment Email",
"Accounts_Enrollment_Email_Default": "<h1>Welcome to <strong>[Site_Name]</strong></h1><p>Go to <a href=\"[Site_URL]\">[Site_URL]</a> and try the best open source chat solution available today!</p>",
"Accounts_Enrollment_Email_Description": "You may use the following placeholders: <br/><ul><li>[name], [fname], [lname] for the user's full name, first name or last name, respectively.</li><li>[email] for the user's email.</li><li>[Site_Name] and [Site_URL] for the Application Name and URL respectively.</li></ul>",
"Accounts_Enrollment_Email_Subject_Default": "Welcome to [Site_Name]",
"Accounts_ForgetUserSessionOnWindowClose": "Forget User Session on Window Close",
"Accounts_Iframe_api_method": "Api Method",
"Accounts_Iframe_api_url": "API URL",
@ -185,9 +183,7 @@
"Accounts_TwoFactorAuthentication_MaxDelta_Description": "The Maximum Delta determines how many tokens are valid at any given time. Tokens are generated every 30 seconds, and are valid for (30 * Maximum Delta) seconds. <br/>Example: With a Maximum Delta set to 10, each token can be used up to 300 seconds before or after it's timestamp. This is useful when the client's clock is not properly synced with the server.",
"Accounts_UseDefaultBlockedDomainsList": "Use Default Blocked Domains List",
"Accounts_UseDNSDomainCheck": "Use DNS Domain Check",
"Accounts_UserAddedEmail_Default": "<h1>Welcome to <strong>[Site_Name]</strong></h1><p>Go to <a href=\"[Site_URL]\">[Site_URL]</a> and try the best open source chat solution available today!</p><p>You may login using your email: [email] and password: [password]. You may be required to change it after your first login.",
"Accounts_UserAddedEmail_Description": "You may use the following placeholders: <br/><ul><li>[name], [fname], [lname] for the user's full name, first name or last name, respectively.</li><li>[email] for the user's email.</li><li>[password] for the user's password.</li><li>[Site_Name] and [Site_URL] for the Application Name and URL respectively.</li></ul>",
"Accounts_UserAddedEmailSubject_Default": "You have been added to [Site_Name]",
"Activate": "Activate",
"Activity": "Activity",
"Add": "Add",
@ -936,6 +932,7 @@
"Direct_message_someone": "Direct message someone",
"Direct_Messages": "Direct Messages",
"Direct_Reply": "Direct Reply",
"Direct_Reply_Advice": "You can directly reply to this email. Do not modify previous emails in the thread.",
"Direct_Reply_Debug": "Debug Direct Reply",
"Direct_Reply_Debug_Description": "[Beware] Enabling Debug mode would display your 'Plain Text Password' in Admin console.",
"Direct_Reply_Delete": "Delete Intercepted Emails",
@ -1026,6 +1023,8 @@
"Email_Placeholder": "Please enter your email address...",
"Email_Placeholder_any": "Please enter email addresses...",
"Email_subject": "Subject",
"email_style_label": "Email Style",
"email_style_description": "Avoid nested selectors",
"Email_verified": "Email verified",
"Emoji": "Emoji",
"EmojiCustomFilesystem": "Custom Emoji Filesystem",
@ -1260,7 +1259,7 @@
"Force_Disable_OpLog_For_Cache_Description": "Will not use OpLog to sync cache even when it's available",
"Force_SSL": "Force SSL",
"Force_SSL_Description": "*Caution!* _Force SSL_ should never be used with reverse proxy. If you have a reverse proxy, you should do the redirect THERE. This option exists for deployments like Heroku, that does not allow the redirect configuration at the reverse proxy.",
"Forgot_password": "Forgot your password",
"Forgot_password": "Forgot your password?",
"Forgot_Password_Description": "You may use the following placeholders: <br/><ul><li>[Forgot_Password_Url] for the password recovery URL.</li><li>[name], [fname], [lname] for the user's full name, first name or last name, respectively.</li><li>[email] for the user's email.</li><li>[Site_Name] and [Site_URL] for the Application Name and URL respectively.</li></ul>",
"Forgot_Password_Email": "Click <a href=\"[Forgot_Password_Url]\">here</a> to reset your password.",
"Forgot_Password_Email_Subject": "[Site_Name] - Password Recovery",
@ -1316,6 +1315,7 @@
"Help_Center": "Help Center",
"Helpers": "Helpers",
"Hex_Color_Preview": "Hex Color Preview",
"Hi_username": "Hi __name__",
"Hidden": "Hidden",
"Hide_Avatars": "Hide Avatars",
"Hide_counter": "Hide counter",
@ -1350,6 +1350,7 @@
"If_you_are_sure_type_in_your_password": "If you are sure type in your password:",
"If_you_are_sure_type_in_your_username": "If you are sure type in your username:",
"If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours": "If you don't have one send an email to [omni@rocket.chat](mailto:omni@rocket.chat) to get yours.",
"If_you_didnt_ask_for_reset_ignore_this_email" : "If you didn't ask for your password reset, you can ignore this email.",
"Iframe_Integration": "Iframe Integration",
"Iframe_Integration_receive_enable": "Enable Receive",
"Iframe_Integration_receive_enable_Description": "Allow parent window to send commands to Rocket.Chat.",
@ -1472,9 +1473,7 @@
"invisible": "invisible",
"Invisible": "Invisible",
"Invitation": "Invitation",
"Invitation_HTML": "Invitation HTML",
"Invitation_HTML_Default": "<h1>You have been invited to <strong>[Site_Name]</strong></h1><p>Go to [Site_URL] and try the best open source chat solution available today!</p>",
"Invitation_HTML_Description": "You may use the following placeholders: <br/><ul><li>[email] for the recipient email.</li><li>[Site_Name] and [Site_URL] for the Application Name and URL respectively.</li></ul>",
"Invitation_Email_Description": "You may use the following placeholders: <br/><ul><li>[email] for the recipient email.</li><li>[Site_Name] and [Site_URL] for the Application Name and URL respectively.</li></ul>",
"Invitation_Subject": "Invitation Subject",
"Invitation_Subject_Default": "You have been invited to [Site_Name]",
"Invite_user_to_join_channel": "Invite one user to join this channel",
@ -1653,6 +1652,7 @@
"Leave_room": "Leave room",
"Leave_Room_Warning": "Are you sure you want to leave the room \"%s\"?",
"Leave_the_current_channel": "Leave the current channel",
"Lets_get_you_new_one": "Let's get you a new one!",
"line": "line",
"List_of_Channels": "List of Channels",
"List_of_Direct_Messages": "List of Direct Messages",
@ -2787,13 +2787,14 @@
"UTF8_Names_Validation": "UTF8 Names Validation",
"UTF8_Names_Validation_Description": "RegExp that will be used to validate usernames and channel names",
"Validate_email_address": "Validate Email Address",
"Verification_email_body": "You have succesfully created an account on [Site_Name]. Please, click on the button below to confirm your email address and finish registration.",
"Verification": "Verification",
"Verification_Description": "You may use the following placeholders: <br/><ul><li>[Verification_Url] for the verification URL.</li><li>[name], [fname], [lname] for the user's full name, first name or last name, respectively.</li><li>[email] for the user's email.</li><li>[Site_Name] and [Site_URL] for the Application Name and URL respectively.</li></ul>",
"Verification_Email": "Click <a href=\"[Verification_Url]\">here</a> to verify your account.",
"Verification_email_sent": "Verification email sent",
"Verification_Email_Subject": "[Site_Name] - Verify your account",
"Verified": "Verified",
"Verify": "Verify",
"Verify_your_email": "Verify your email",
"Version": "Version",
"Video_Chat_Window": "Video Chat",
"Video_Conference": "Video Conference",
@ -2840,6 +2841,7 @@
"Viewing_room_administration": "Viewing room administration",
"Visibility": "Visibility",
"Visible": "Visible",
"Visit_Site_Url_and_try_the_best_open_source_chat_solution_available_today": "Visit __Site_URL__ and try the best open source chat solution available today!",
"Visitor": "Visitor",
"Visitor_Info": "Visitor Info",
"Visitor_Navigation": "Visitor Navigation",
@ -2872,6 +2874,7 @@
"Website": "Website",
"Wednesday": "Wednesday",
"Welcome": "Welcome <em>%s</em>.",
"Welcome_to": "Welcome to __Site_Name__",
"Welcome_to_the": "Welcome to the",
"Why_do_you_want_to_report_question_mark": "Why do you want to report?",
"will_be_able_to": "will be able to",

@ -9,7 +9,7 @@
"resolved": "https://registry.npmjs.org/katex/-/katex-0.9.0.tgz",
"integrity": "sha512-lp3x90LT1tDZBW2tjLheJ98wmRMRjUHwk4QpaswT9bhqoQZ+XA4cPcjcQBxgOQNwaOSt6ZeL/a6GKQ1of3LFxQ==",
"requires": {
"match-at": "0.1.1"
"match-at": "^0.1.1"
}
},
"match-at": {

@ -28,6 +28,7 @@ Package.onUse(function(api) {
api.use('rocketchat:streamer');
api.use('rocketchat:version');
api.use('rocketchat:logger');
api.use('rocketchat:mailer');
api.use('rocketchat:custom-oauth');
api.use('rocketchat:authorization', { unordered: true });
api.use('rocketchat:push-notifications', { unordered: true });
@ -238,6 +239,7 @@ Package.onUse(function(api) {
api.addFiles('client/views/customFieldsForm.js', 'client');
api.addFiles('startup/defaultRoomTypes.js');
api.addFiles('startup/index.js', 'server');
// VERSION
api.addFiles('rocketchat.info');

@ -1,17 +1,17 @@
import s from 'underscore.string';
import * as Mailer from 'meteor/rocketchat:mailer';
let contentHeader;
RocketChat.settings.get('Email_Header', (key, value) => {
contentHeader = RocketChat.placeholders.replace(value || '');
});
let contentFooter;
RocketChat.settings.get('Email_Footer', (key, value) => {
contentFooter = RocketChat.placeholders.replace(value || '');
let advice = '';
let goToMessage = '';
Meteor.startup(() => {
RocketChat.settings.get('email_style', function() {
goToMessage = Mailer.inlinecss('<p><a class=\'btn\' href="[room_path]">{Offline_Link_Message}</a></p>');
});
Mailer.getTemplate('Email_Footer_Direct_Reply', (value) => {
advice = value;
});
});
const divisorMessage = '<hr style="margin: 20px auto; border: none; border-bottom: 1px solid #dddddd;">';
function getEmailContent({ message, user, room }) {
const lng = (user && user.language) || RocketChat.settings.get('language') || 'en';
@ -75,76 +75,50 @@ function getEmailContent({ message, user, room }) {
return header;
}
function getMessageLink(room, sub) {
const roomPath = RocketChat.roomTypes.getURL(room.t, sub);
const style = [
'color: #fff;',
'padding: 9px 12px;',
'border-radius: 4px;',
'background-color: #04436a;',
'text-decoration: none;',
].join(' ');
const message = TAPi18n.__('Offline_Link_Message');
return `<p style="text-align:center;margin-bottom:8px;"><a style="${ style }" href="${ roomPath }">${ message }</a>`;
}
export function sendEmail({ message, user, subscription, room, emailAddress, hasMentionToUser }) {
let emailSubject;
const username = RocketChat.settings.get('UI_Use_Real_Name') ? message.u.name : message.u.username;
let subjectKey = 'Offline_Mention_All_Email';
if (room.t === 'd') {
emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_DM_Email'), {
user: username,
room: RocketChat.roomTypes.getRoomName(room.t, room),
});
subjectKey = 'Offline_DM_Email';
} else if (hasMentionToUser) {
emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_Mention_Email'), {
user: username,
room: RocketChat.roomTypes.getRoomName(room.t, room),
});
} else {
emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_Mention_All_Email'), {
user: username,
room: RocketChat.roomTypes.getRoomName(room.t, room),
});
subjectKey = 'Offline_Mention_Email';
}
const emailSubject = Mailer.replace(RocketChat.settings.get(subjectKey), {
user: username,
room: RocketChat.roomTypes.getRoomName(room.t, room),
});
const content = getEmailContent({
message,
user,
room,
});
const link = getMessageLink(room, subscription);
if (RocketChat.settings.get('Direct_Reply_Enable')) {
contentFooter = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer_Direct_Reply') || '');
}
const room_path = RocketChat.roomTypes.getURL(room.t, subscription);
const email = {
to: emailAddress,
subject: emailSubject,
html: contentHeader + content + divisorMessage + link + contentFooter,
html: content + goToMessage + (RocketChat.settings.get('Direct_Reply_Enable') ? advice : ''),
data: {
room_path,
},
};
// using user full-name/channel name in from address
if (room.t === 'd') {
email.from = `${ String(message.u.name).replace(/@/g, '%40').replace(/[<>,]/g, '') } <${ RocketChat.settings.get('From_Email') }>`;
} else {
email.from = `${ String(room.name).replace(/@/g, '%40').replace(/[<>,]/g, '') } <${ RocketChat.settings.get('From_Email') }>`;
}
const from = room.t === 'd' ? message.u.name : room.name; // using user full-name/channel name in from address
email.from = `${ String(from).replace(/@/g, '%40').replace(/[<>,]/g, '') } <${ RocketChat.settings.get('From_Email') }>`;
// If direct reply enabled, email content with headers
if (RocketChat.settings.get('Direct_Reply_Enable')) {
const replyto = RocketChat.settings.get('Direct_Reply_ReplyTo') ? RocketChat.settings.get('Direct_Reply_ReplyTo') : RocketChat.settings.get('Direct_Reply_Username');
const replyto = RocketChat.settings.get('Direct_Reply_ReplyTo') || RocketChat.settings.get('Direct_Reply_Username');
email.headers = {
// Reply-To header with format "username+messageId@domain"
'Reply-To': `${ replyto.split('@')[0].split(RocketChat.settings.get('Direct_Reply_Separator'))[0] }${ RocketChat.settings.get('Direct_Reply_Separator') }${ message._id }@${ replyto.split('@')[1] }`,
};
}
Meteor.defer(() => {
RocketChat.metrics.notificationsSent.inc({ notification_type: 'email' });
Email.send(email);
});
RocketChat.metrics.notificationsSent.inc({ notification_type: 'email' });
return Mailer.send(email);
}
export function shouldNotifyEmail({

@ -1,6 +1,13 @@
/* globals Gravatar */
import _ from 'underscore';
import s from 'underscore.string';
import * as Mailer from 'meteor/rocketchat:mailer';
let html = '';
Meteor.startup(() => {
Mailer.getTemplate('Accounts_UserAddedEmail_Email', (template) => {
html = template;
});
});
function validateUserData(userId, userData) {
const existingRoles = _.pluck(RocketChat.authz.getRoles(), '_id');
@ -89,7 +96,6 @@ function validateUserData(userId, userData) {
RocketChat.saveUser = function(userId, userData) {
validateUserData(userId, userData);
const user = RocketChat.models.Users.findOneById(userId);
if (!userData._id) {
RocketChat.validateEmailDomain(userData.email);
@ -125,44 +131,28 @@ RocketChat.saveUser = function(userId, userData) {
Meteor.users.update({ _id }, updateUser);
if (userData.sendWelcomeEmail) {
const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
let subject;
let html;
if (RocketChat.settings.get('Accounts_UserAddedEmail_Customized')) {
subject = RocketChat.settings.get('Accounts_UserAddedEmailSubject');
html = RocketChat.settings.get('Accounts_UserAddedEmail');
} else {
subject = TAPi18n.__('Accounts_UserAddedEmailSubject_Default', { lng: user.language || RocketChat.settings.get('language') || 'en' });
html = TAPi18n.__('Accounts_UserAddedEmail_Default', { lng: user.language || RocketChat.settings.get('language') || 'en' });
}
subject = RocketChat.placeholders.replace(subject);
html = RocketChat.placeholders.replace(html, {
name: s.escapeHTML(userData.name),
email: s.escapeHTML(userData.email),
password: s.escapeHTML(userData.password),
});
const subject = RocketChat.settings.get('Accounts_UserAddedEmail_Subject');
const email = {
to: userData.email,
from: RocketChat.settings.get('From_Email'),
subject,
html: header + html + footer,
html,
data: {
name: s.escapeHTML(userData.name),
email: s.escapeHTML(userData.email),
password: s.escapeHTML(userData.password),
},
};
Meteor.defer(function() {
try {
Email.send(email);
} catch (error) {
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${ error.message }`, {
function: 'RocketChat.saveUser',
message: error.message,
});
}
});
try {
Mailer.send(email);
} catch (error) {
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${ error.message }`, {
function: 'RocketChat.saveUser',
message: error.message,
});
}
}
userData._id = _id;
@ -178,82 +168,81 @@ RocketChat.saveUser = function(userId, userData) {
}
return _id;
} else {
if (!RocketChat.settings.get('Accounts_AllowUserProfileChange') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-info') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-password')) {
throw new Meteor.Error('error-action-not-allowed', 'Edit user profile is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
}
if (!RocketChat.settings.get('Accounts_AllowUserProfileChange') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-info') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-password')) {
throw new Meteor.Error('error-action-not-allowed', 'Edit user profile is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
if (userData.username && !RocketChat.settings.get('Accounts_AllowUsernameChange') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-info')) {
throw new Meteor.Error('error-action-not-allowed', 'Edit username is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
if (userData.username && !RocketChat.settings.get('Accounts_AllowUsernameChange') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-info')) {
throw new Meteor.Error('error-action-not-allowed', 'Edit username is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
if (userData.name && !RocketChat.settings.get('Accounts_AllowRealNameChange') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-info')) {
throw new Meteor.Error('error-action-not-allowed', 'Edit user real name is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
if (userData.name && !RocketChat.settings.get('Accounts_AllowRealNameChange') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-info')) {
throw new Meteor.Error('error-action-not-allowed', 'Edit user real name is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
if (userData.email && !RocketChat.settings.get('Accounts_AllowEmailChange') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-info')) {
throw new Meteor.Error('error-action-not-allowed', 'Edit user email is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
if (userData.email && !RocketChat.settings.get('Accounts_AllowEmailChange') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-info')) {
throw new Meteor.Error('error-action-not-allowed', 'Edit user email is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
if (userData.password && !RocketChat.settings.get('Accounts_AllowPasswordChange') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-password')) {
throw new Meteor.Error('error-action-not-allowed', 'Edit user password is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
if (userData.password && !RocketChat.settings.get('Accounts_AllowPasswordChange') && !RocketChat.authz.hasPermission(userId, 'edit-other-user-password')) {
throw new Meteor.Error('error-action-not-allowed', 'Edit user password is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
// update user
if (userData.username) {
RocketChat.setUsername(userData._id, userData.username);
}
// update user
if (userData.username) {
RocketChat.setUsername(userData._id, userData.username);
}
if (userData.name) {
RocketChat.setRealName(userData._id, userData.name);
}
if (userData.name) {
RocketChat.setRealName(userData._id, userData.name);
}
if (userData.email) {
const shouldSendVerificationEmailToUser = userData.verified !== true;
RocketChat.setEmail(userData._id, userData.email, shouldSendVerificationEmailToUser);
}
if (userData.email) {
const shouldSendVerificationEmailToUser = userData.verified !== true;
RocketChat.setEmail(userData._id, userData.email, shouldSendVerificationEmailToUser);
}
if (userData.password && userData.password.trim() && RocketChat.authz.hasPermission(userId, 'edit-other-user-password') && RocketChat.passwordPolicy.validate(userData.password)) {
Accounts.setPassword(userData._id, userData.password.trim());
}
if (userData.password && userData.password.trim() && RocketChat.authz.hasPermission(userId, 'edit-other-user-password') && RocketChat.passwordPolicy.validate(userData.password)) {
Accounts.setPassword(userData._id, userData.password.trim());
}
const updateUser = {
$set: {},
};
const updateUser = {
$set: {},
};
if (userData.roles) {
updateUser.$set.roles = userData.roles;
}
if (userData.roles) {
updateUser.$set.roles = userData.roles;
}
if (userData.settings) {
updateUser.$set.settings = { preferences: userData.settings.preferences };
}
if (userData.settings) {
updateUser.$set.settings = { preferences: userData.settings.preferences };
}
if (typeof userData.requirePasswordChange !== 'undefined') {
updateUser.$set.requirePasswordChange = userData.requirePasswordChange;
}
if (typeof userData.requirePasswordChange !== 'undefined') {
updateUser.$set.requirePasswordChange = userData.requirePasswordChange;
}
if (typeof userData.verified === 'boolean') {
updateUser.$set['emails.0.verified'] = userData.verified;
}
if (typeof userData.verified === 'boolean') {
updateUser.$set['emails.0.verified'] = userData.verified;
}
Meteor.users.update({ _id: userData._id }, updateUser);
Meteor.users.update({ _id: userData._id }, updateUser);
return true;
}
return true;
};

@ -1,5 +1,10 @@
import _ from 'underscore';
import s from 'underscore.string';
import * as Mailer from 'meteor/rocketchat:mailer';
let html = '';
Meteor.startup(() => {
Mailer.getTemplate('Invitation_Email', (value) => {
html = value;
});
});
Meteor.methods({
sendInvitationEmail(emails) {
@ -14,41 +19,20 @@ Meteor.methods({
method: 'sendInvitationEmail',
});
}
const rfcMailPattern = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
const validEmails = _.compact(_.map(emails, function(email) {
if (rfcMailPattern.test(email)) {
return email;
}
}));
const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
let html;
let subject;
const user = Meteor.user();
const lng = user.language || RocketChat.settings.get('language') || 'en';
if (RocketChat.settings.get('Invitation_Customized')) {
subject = RocketChat.settings.get('Invitation_Subject');
html = RocketChat.settings.get('Invitation_HTML');
} else {
subject = TAPi18n.__('Invitation_Subject_Default', {
lng,
});
html = TAPi18n.__('Invitation_HTML_Default', {
lng,
});
}
subject = RocketChat.placeholders.replace(subject);
validEmails.forEach((email) => {
this.unblock();
html = RocketChat.placeholders.replace(html, {
email: s.escapeHTML(email),
});
const validEmails = emails.filter(Mailer.checkAddressFormat);
const subject = RocketChat.settings.get('Invitation_Subject');
return validEmails.filter((email) => {
try {
Email.send({
return Mailer.send({
to: email,
from: RocketChat.settings.get('From_Email'),
subject,
html: header + html + footer,
html,
data: {
email,
},
});
} catch ({ message }) {
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${ message }`, {
@ -57,6 +41,5 @@ Meteor.methods({
});
}
});
return validEmails;
},
});

@ -1,3 +1,5 @@
import * as Mailer from 'meteor/rocketchat:mailer';
Meteor.methods({
sendSMTPTestEmail() {
if (!Meteor.userId()) {
@ -11,16 +13,12 @@ Meteor.methods({
method: 'sendSMTPTestEmail',
});
}
this.unblock();
const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
console.log(`Sending test email to ${ user.emails[0].address }`);
try {
Email.send({
Mailer.send({
to: user.emails[0].address,
from: RocketChat.settings.get('From_Email'),
subject: 'SMTP Test Email',
html: `${ header }<p>You have successfully sent an email</p>${ footer }`,
html: '<p>You have successfully sent an email</p>',
});
} catch ({ message }) {
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${ message }`, {

@ -0,0 +1,425 @@
RocketChat.settings.addGroup('Email', function() {
this.section('Style', function() {
this.add('email_style', `html, body{ font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Helvetica Neue','Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Meiryo UI',Arial,sans-serif; }
body {
width: 100%;
height: 100%;
}
a {
color: #1D74F5;
font-weight: bold;
text-decoration: none;
line-height: 1.8;
padding-left: 2px;
padding-right: 2px;
}
p {
margin: 1rem 0;
}
.btn {
text-decoration: none;
color: #FFF;
background-color: #1D74F5;
padding: 12px 18px;
font-weight: 500;
font-size: 14px;
margin-top: 8px;
text-align: center;
cursor: pointer;
display: inline-block;
border-radius: 2px;
}
ol, ul, div {
list-style-position: inside;
padding: 16px 0 ;
}
li {
padding: 8px 0;
font-weight: 600;
}
.wrap {
width: 100%;
clear: both;
}
h1,h2,h3,h4,h5,h6 {
line-height: 1.1; margin:0 0 16px 0; color: #000;
}
h1 { font-weight: 100; font-size: 44px;}
h2 { font-weight: 600; font-size: 30px; color: #2F343D;}
h3 { font-weight: 100; font-size: 27px;}
h4 { font-weight: 500; font-size: 14px; color: #2F343D;}
h5 { font-weight: 500; font-size: 13px; line-height: 1.6; color: #2F343D}
h6 { font-weight: 500; font-size: 10px; color: #6c727A; line-height: 1.7;}
.container {
display: block;
max-width: 640px;
margin: 0 auto; /* makes it centered */
clear: both;
border-radius: 2px;
}
.content {
padding: 36px;
}
.header-content {
padding-top: 36px;
padding-bottom: 36px;
padding-left: 36px;
padding-right: 36px;
max-width: 640px;
margin: 0 auto;
display: block;
}
.lead {
margin-bottom: 32px;
color: #2f343d;
line-height: 22px;
font-size: 14px;
}
.advice {
height: 20px;
color: #9EA2A8;
font-size: 12px;
font-weight: normal;
margin-bottom: 0;
}
.social {
font-size: 12px
}
`, {
type: 'code',
code: 'css',
multiline: true,
i18nLabel: 'email_style_label',
i18nDescription: 'email_style_description',
});
});
this.section('Subject', function() {
this.add('Offline_DM_Email', '[[Site_Name]] You have been direct messaged by [User]', {
type: 'code',
code: 'text',
multiline: true,
i18nLabel: 'Offline_DM_Email',
i18nDescription: 'Offline_Email_Subject_Description',
});
this.add('Offline_Mention_Email', '[[Site_Name]] You have been mentioned by [User] in #[Room]', {
type: 'code',
code: 'text',
multiline: true,
i18nLabel: 'Offline_Mention_Email',
i18nDescription: 'Offline_Email_Subject_Description',
});
this.add('Offline_Mention_All_Email', '[User] has posted a message in #[Room]', {
type: 'code',
code: 'text',
multiline: true,
i18nLabel: 'Offline_Mention_All_Email',
i18nDescription: 'Offline_Email_Subject_Description',
});
});
this.section('Header_and_Footer', function() {
this.add('Email_Header', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http: //www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http: //www.w3.org/1999/xhtml"><head><!-- If you delete this tag, the sky will fall on your head --><meta name="viewport" content="width=device-width" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Rocket.Chat Cloud</title></head><body bgcolor="#F7F8FA"><table bgcolor="#F7F8FA" width="100%"><tr><td><!-- HEADER --><table class="wrap" bgcolor="#F7F8FA"><tr><td class="header container"><div class="header-content"><table bgcolor="#F7F8FA" width="100%"><tr><td><img src="[Site_Url]/assets/logo" alt="Rocket.chat-logo" width="150px" /></td></tr></table></div></td></tr></table><!-- /HEADER --></td></tr><tr><td><!-- BODY --><table class="wrap"><tr><td class="container" bgcolor="#FFFFFF"><div class="content"><table><tr><td>', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Header',
});
this.add('Email_Footer', '</td></tr></table></div></td></tr></table><!-- /BODY --></td></tr><tr style="margin: 0; padding: 0;"><td style="margin: 0; padding: 0;"><!-- FOOTER --><table class="wrap"><tr><td class="container"><!-- content --><div class="content"><table width="100%"><tr><td align="center" class="social"><a href="https: //rocket.chat/blog">Blog</a> | <a href="https: //github.com/RocketChat">Github</a> | <a href="https: //www.facebook.com/RocketChatApp">Facebook</a> | <a href="https: //www.instagram.com/rocket.chat">Instagram</a></td></tr><tr><td align="center"><h6>© Rocket.Chat Technologies Corp.</h6><h6>Made with ❤ in 🇧🇷 🇨🇦 🇩🇪 🇮🇳 🇬🇧 🇺🇸 </h6></td></tr></table></div><!-- /content --></td></tr></table><!-- /FOOTER --></td></tr></table></body></html>', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Footer',
});
this.add('Email_Footer_Direct_Reply', '<p class="advice">{Direct_Reply_Advice}</p>', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Footer_Direct_Reply',
});
});
this.section('Direct_Reply', function() {
this.add('Direct_Reply_Enable', false, {
type: 'boolean',
env: true,
i18nLabel: 'Direct_Reply_Enable',
});
this.add('Direct_Reply_Debug', false, {
type: 'boolean',
env: true,
i18nLabel: 'Direct_Reply_Debug',
i18nDescription: 'Direct_Reply_Debug_Description',
});
this.add('Direct_Reply_Protocol', 'IMAP', {
type: 'select',
values: [
{
key: 'IMAP',
i18nLabel: 'IMAP',
}, {
key: 'POP',
i18nLabel: 'POP',
},
],
env: true,
i18nLabel: 'Protocol',
});
this.add('Direct_Reply_Host', '', {
type: 'string',
env: true,
i18nLabel: 'Host',
});
this.add('Direct_Reply_Port', '', {
type: 'string',
env: true,
i18nLabel: 'Port',
});
this.add('Direct_Reply_IgnoreTLS', false, {
type: 'boolean',
env: true,
i18nLabel: 'IgnoreTLS',
});
this.add('Direct_Reply_Frequency', 5, {
type: 'int',
env: true,
i18nLabel: 'Direct_Reply_Frequency',
enableQuery: {
_id: 'Direct_Reply_Protocol',
value: 'POP',
},
});
this.add('Direct_Reply_Delete', true, {
type: 'boolean',
env: true,
i18nLabel: 'Direct_Reply_Delete',
enableQuery: {
_id: 'Direct_Reply_Protocol',
value: 'IMAP',
},
});
this.add('Direct_Reply_Separator', '+', {
type: 'select',
values: [
{
key: '!',
i18nLabel: '!',
}, {
key: '#',
i18nLabel: '#',
}, {
key: '$',
i18nLabel: '$',
}, {
key: '%',
i18nLabel: '%',
}, {
key: '&',
i18nLabel: '&',
}, {
key: '\'',
i18nLabel: '\'',
}, {
key: '*',
i18nLabel: '*',
}, {
key: '+',
i18nLabel: '+',
}, {
key: '-',
i18nLabel: '-',
}, {
key: '/',
i18nLabel: '/',
}, {
key: '=',
i18nLabel: '=',
}, {
key: '?',
i18nLabel: '?',
}, {
key: '^',
i18nLabel: '^',
}, {
key: '_',
i18nLabel: '_',
}, {
key: '`',
i18nLabel: '`',
}, {
key: '{',
i18nLabel: '{',
}, {
key: '|',
i18nLabel: '|',
}, {
key: '}',
i18nLabel: '}',
}, {
key: '~',
i18nLabel: '~',
},
],
env: true,
i18nLabel: 'Direct_Reply_Separator',
});
this.add('Direct_Reply_Username', '', {
type: 'string',
env: true,
i18nLabel: 'Username',
placeholder: 'email@domain',
});
this.add('Direct_Reply_ReplyTo', '', {
type: 'string',
env: true,
i18nLabel: 'ReplyTo',
placeholder: 'email@domain',
});
return this.add('Direct_Reply_Password', '', {
type: 'password',
env: true,
i18nLabel: 'Password',
});
});
this.section('SMTP', function() {
this.add('SMTP_Protocol', 'smtp', {
type: 'select',
values: [
{
key: 'smtp',
i18nLabel: 'smtp',
}, {
key: 'smtps',
i18nLabel: 'smtps',
},
],
env: true,
i18nLabel: 'Protocol',
});
this.add('SMTP_Host', '', {
type: 'string',
env: true,
i18nLabel: 'Host',
});
this.add('SMTP_Port', '', {
type: 'string',
env: true,
i18nLabel: 'Port',
});
this.add('SMTP_IgnoreTLS', true, {
type: 'boolean',
env: true,
i18nLabel: 'IgnoreTLS',
enableQuery: {
_id: 'SMTP_Protocol',
value: 'smtp',
},
});
this.add('SMTP_Pool', true, {
type: 'boolean',
env: true,
i18nLabel: 'Pool',
});
this.add('SMTP_Username', '', {
type: 'string',
env: true,
i18nLabel: 'Username',
autocomplete: false,
});
this.add('SMTP_Password', '', {
type: 'password',
env: true,
i18nLabel: 'Password',
autocomplete: false,
});
this.add('From_Email', '', {
type: 'string',
placeholder: 'email@domain',
});
return this.add('SMTP_Test_Button', 'sendSMTPTestEmail', {
type: 'action',
actionText: 'Send_a_test_mail_to_my_user',
});
});
this.section('Registration', function() {
this.add('Accounts_Enrollment_Email_Subject', '{Welcome_to Site_name}', {
type: 'string',
i18nLabel: 'Subject',
});
this.add('Accounts_Enrollment_Email', '<h2>{Welcome_to Site_Name}</h2><p>{Visit_Site_Url_and_try_the_best_open_source_chat_solution_available_today}</p><a class="btn" target="_blank" href="[Site_URL]">{Login}</a>', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Body',
});
});
this.section('Registration_via_Admin', function() {
this.add('Accounts_UserAddedEmail_Subject', '{Welcome_to Site_Name}', {
type: 'string',
i18nLabel: 'Subject',
});
this.add('Accounts_UserAddedEmail_Email', '<h2>{Welcome_to Site_Name}</h2><p>{Visit_Site_Url_and_try_the_best_open_source_chat_solution_available_today}</p><a class="btn" target="_blank" href="[Site_URL]">{Login}</a>', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Body',
i18nDescription: 'Accounts_UserAddedEmail_Description',
});
});
this.section('Verification', function() {
this.add('Verification_Email_Subject', '{Verification_Email_Subject}', {
type: 'string',
i18nLabel: 'Subject',
});
this.add('Verification_Email', '<h2>{Hi_username}</h2><p>{Verification_email_body}</p><a class="btn" target="_blank" href="[Verification_Url]">{Verify_your_email}</a>', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Body',
i18nDescription: 'Verification_Description',
});
});
this.section('Invitation', function() {
this.add('Invitation_Subject', '{Invitation_Subject_Default}', {
type: 'string',
i18nLabel: 'Subject',
});
this.add('Invitation_Email', '<h2>{Welcome_to Site_Name}</h2><p>{Visit_Site_Url_and_try_the_best_open_source_chat_solution_available_today}</p><a class="btn" href="[Site_URL]">{Join_Chat}</a>', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Body',
i18nDescription: 'Invitation_Email_Description',
});
});
this.section('Forgot_password_section', function() {
this.add('Forgot_Password_Email_Subject', '{Forgot_Password_Email_Subject}', {
type: 'string',
i18nLabel: 'Subject',
});
this.add('Forgot_Password_Email', '<h2>{Forgot_password}</h2><p>{Lets_get_you_new_one}</p><a class="btn" href="[Forgot_Password_Url]">{Reset}</a><p class="advice">{If_you_didnt_ask_for_reset_ignore_this_email}</p>', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Body',
i18nDescription: 'Forgot_Password_Description',
});
});
});

@ -1,3 +1,5 @@
import './email';
// Insert server unique id if it doesn't exist
RocketChat.settings.add('uniqueID', process.env.DEPLOYMENT_ID || Random.id(), {
public: true,
@ -884,418 +886,6 @@ RocketChat.settings.addGroup('General', function() {
});
});
RocketChat.settings.addGroup('Email', function() {
this.section('Subject', function() {
this.add('Offline_DM_Email', '[[Site_Name]] You have been direct messaged by [User]', {
type: 'code',
code: 'text',
multiline: true,
i18nLabel: 'Offline_DM_Email',
i18nDescription: 'Offline_Email_Subject_Description',
});
this.add('Offline_Mention_Email', '[[Site_Name]] You have been mentioned by [User] in #[Room]', {
type: 'code',
code: 'text',
multiline: true,
i18nLabel: 'Offline_Mention_Email',
i18nDescription: 'Offline_Email_Subject_Description',
});
return this.add('Offline_Mention_All_Email', '[User] has posted a message in #[Room]', {
type: 'code',
code: 'text',
multiline: true,
i18nLabel: 'Offline_Mention_All_Email',
i18nDescription: 'Offline_Email_Subject_Description',
});
});
this.section('Header_and_Footer', function() {
this.add('Email_Header', '<html><table border="0" cellspacing="0" cellpadding="0" width="100%" bgcolor="#f3f3f3" style="color:#4a4a4a;font-family: Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;border-collapse:collapse;border-spacing:0;margin:0 auto"><tr><td style="padding:1em"><table border="0" cellspacing="0" cellpadding="0" align="center" width="100%" style="width:100%;margin:0 auto;max-width:800px"><tr><td bgcolor="#ffffff" style="background-color:#ffffff; border: 1px solid #DDD; font-size: 10pt; font-family: Helvetica,Arial,sans-serif;"><table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td style="background-color: #04436a;"><h1 style="font-family: Helvetica,Arial,sans-serif; padding: 0 1em; margin: 0; line-height: 70px; color: #FFF;">[Site_Name]</h1></td></tr><tr><td style="padding: 1em; font-size: 10pt; font-family: Helvetica,Arial,sans-serif;">', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Header',
});
this.add('Email_Footer', '</td></tr></table></td></tr><tr><td border="0" cellspacing="0" cellpadding="0" width="100%" style="font-family: Helvetica,Arial,sans-serif; max-width: 800px; margin: 0 auto; padding: 1.5em; text-align: center; font-size: 8pt; color: #999;">Powered by <a href="https://rocket.chat" target="_blank">Rocket.Chat</a></td></tr></table></td></tr></table></html>', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Footer',
});
return this.add('Email_Footer_Direct_Reply', '</td></tr></table></td></tr><tr><td border="0" cellspacing="0" cellpadding="0" width="100%" style="font-family: Helvetica,Arial,sans-serif; max-width: 800px; margin: 0 auto; padding: 1.5em; text-align: center; font-size: 8pt; color: #999;">You can directly reply to this email.<br>Do not modify previous emails in the thread.<br>Powered by <a href="https://rocket.chat" target="_blank">Rocket.Chat</a></td></tr></table></td></tr></table></html>', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Footer_Direct_Reply',
});
});
this.section('Direct_Reply', function() {
this.add('Direct_Reply_Enable', false, {
type: 'boolean',
env: true,
i18nLabel: 'Direct_Reply_Enable',
});
this.add('Direct_Reply_Debug', false, {
type: 'boolean',
env: true,
i18nLabel: 'Direct_Reply_Debug',
i18nDescription: 'Direct_Reply_Debug_Description',
});
this.add('Direct_Reply_Protocol', 'IMAP', {
type: 'select',
values: [
{
key: 'IMAP',
i18nLabel: 'IMAP',
}, {
key: 'POP',
i18nLabel: 'POP',
},
],
env: true,
i18nLabel: 'Protocol',
});
this.add('Direct_Reply_Host', '', {
type: 'string',
env: true,
i18nLabel: 'Host',
});
this.add('Direct_Reply_Port', '', {
type: 'string',
env: true,
i18nLabel: 'Port',
});
this.add('Direct_Reply_IgnoreTLS', false, {
type: 'boolean',
env: true,
i18nLabel: 'IgnoreTLS',
});
this.add('Direct_Reply_Frequency', 5, {
type: 'int',
env: true,
i18nLabel: 'Direct_Reply_Frequency',
enableQuery: {
_id: 'Direct_Reply_Protocol',
value: 'POP',
},
});
this.add('Direct_Reply_Delete', true, {
type: 'boolean',
env: true,
i18nLabel: 'Direct_Reply_Delete',
enableQuery: {
_id: 'Direct_Reply_Protocol',
value: 'IMAP',
},
});
this.add('Direct_Reply_Separator', '+', {
type: 'select',
values: [
{
key: '!',
i18nLabel: '!',
}, {
key: '#',
i18nLabel: '#',
}, {
key: '$',
i18nLabel: '$',
}, {
key: '%',
i18nLabel: '%',
}, {
key: '&',
i18nLabel: '&',
}, {
key: '\'',
i18nLabel: '\'',
}, {
key: '*',
i18nLabel: '*',
}, {
key: '+',
i18nLabel: '+',
}, {
key: '-',
i18nLabel: '-',
}, {
key: '/',
i18nLabel: '/',
}, {
key: '=',
i18nLabel: '=',
}, {
key: '?',
i18nLabel: '?',
}, {
key: '^',
i18nLabel: '^',
}, {
key: '_',
i18nLabel: '_',
}, {
key: '`',
i18nLabel: '`',
}, {
key: '{',
i18nLabel: '{',
}, {
key: '|',
i18nLabel: '|',
}, {
key: '}',
i18nLabel: '}',
}, {
key: '~',
i18nLabel: '~',
},
],
env: true,
i18nLabel: 'Direct_Reply_Separator',
});
this.add('Direct_Reply_Username', '', {
type: 'string',
env: true,
i18nLabel: 'Username',
placeholder: 'email@domain',
});
this.add('Direct_Reply_ReplyTo', '', {
type: 'string',
env: true,
i18nLabel: 'ReplyTo',
placeholder: 'email@domain',
});
return this.add('Direct_Reply_Password', '', {
type: 'password',
env: true,
i18nLabel: 'Password',
});
});
this.section('SMTP', function() {
this.add('SMTP_Protocol', 'smtp', {
type: 'select',
values: [
{
key: 'smtp',
i18nLabel: 'smtp',
}, {
key: 'smtps',
i18nLabel: 'smtps',
},
],
env: true,
i18nLabel: 'Protocol',
});
this.add('SMTP_Host', '', {
type: 'string',
env: true,
i18nLabel: 'Host',
});
this.add('SMTP_Port', '', {
type: 'string',
env: true,
i18nLabel: 'Port',
});
this.add('SMTP_IgnoreTLS', false, {
type: 'boolean',
env: true,
i18nLabel: 'IgnoreTLS',
enableQuery: {
_id: 'SMTP_Protocol',
value: 'smtp',
},
});
this.add('SMTP_Pool', true, {
type: 'boolean',
env: true,
i18nLabel: 'Pool',
});
this.add('SMTP_Username', '', {
type: 'string',
env: true,
i18nLabel: 'Username',
autocomplete: false,
});
this.add('SMTP_Password', '', {
type: 'password',
env: true,
i18nLabel: 'Password',
autocomplete: false,
});
this.add('From_Email', '', {
type: 'string',
placeholder: 'email@domain',
});
return this.add('SMTP_Test_Button', 'sendSMTPTestEmail', {
type: 'action',
actionText: 'Send_a_test_mail_to_my_user',
});
});
this.section('Invitation', function() {
this.add('Invitation_Customized', false, {
type: 'boolean',
i18nLabel: 'Custom',
});
this.add('Invitation_Subject', '', {
type: 'string',
i18nLabel: 'Subject',
enableQuery: {
_id: 'Invitation_Customized',
value: true,
},
i18nDefaultQuery: {
_id: 'Invitation_Customized',
value: false,
},
});
return this.add('Invitation_HTML', '', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Body',
i18nDescription: 'Invitation_HTML_Description',
enableQuery: {
_id: 'Invitation_Customized',
value: true,
},
i18nDefaultQuery: {
_id: 'Invitation_Customized',
value: false,
},
});
});
this.section('Registration', function() {
this.add('Accounts_Enrollment_Customized', false, {
type: 'boolean',
i18nLabel: 'Custom',
});
this.add('Accounts_Enrollment_Email_Subject', '', {
type: 'string',
i18nLabel: 'Subject',
enableQuery: {
_id: 'Accounts_Enrollment_Customized',
value: true,
},
i18nDefaultQuery: {
_id: 'Accounts_Enrollment_Customized',
value: false,
},
});
return this.add('Accounts_Enrollment_Email', '', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Body',
enableQuery: {
_id: 'Accounts_Enrollment_Customized',
value: true,
},
i18nDefaultQuery: {
_id: 'Accounts_Enrollment_Customized',
value: false,
},
});
});
this.section('Registration_via_Admin', function() {
this.add('Accounts_UserAddedEmail_Customized', false, {
type: 'boolean',
i18nLabel: 'Custom',
});
this.add('Accounts_UserAddedEmailSubject', '', {
type: 'string',
i18nLabel: 'Subject',
enableQuery: {
_id: 'Accounts_UserAddedEmail_Customized',
value: true,
},
i18nDefaultQuery: {
_id: 'Accounts_UserAddedEmail_Customized',
value: false,
},
});
return this.add('Accounts_UserAddedEmail', '', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Body',
i18nDescription: 'Accounts_UserAddedEmail_Description',
enableQuery: {
_id: 'Accounts_UserAddedEmail_Customized',
value: true,
},
i18nDefaultQuery: {
_id: 'Accounts_UserAddedEmail_Customized',
value: false,
},
});
});
this.section('Forgot_password_section', function() {
this.add('Forgot_Password_Customized', false, {
type: 'boolean',
i18nLabel: 'Custom',
});
this.add('Forgot_Password_Email_Subject', '', {
type: 'string',
i18nLabel: 'Subject',
enableQuery: {
_id: 'Forgot_Password_Customized',
value: true,
},
i18nDefaultQuery: {
_id: 'Forgot_Password_Customized',
value: false,
},
});
return this.add('Forgot_Password_Email', '', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Body',
i18nDescription: 'Forgot_Password_Description',
enableQuery: {
_id: 'Forgot_Password_Customized',
value: true,
},
i18nDefaultQuery: {
_id: 'Forgot_Password_Customized',
value: false,
},
});
});
return this.section('Verification', function() {
this.add('Verification_Customized', false, {
type: 'boolean',
i18nLabel: 'Custom',
});
this.add('Verification_Email_Subject', '', {
type: 'string',
i18nLabel: 'Subject',
enableQuery: {
_id: 'Verification_Customized',
value: true,
},
i18nDefaultQuery: {
_id: 'Verification_Customized',
value: false,
},
});
return this.add('Verification_Email', '', {
type: 'code',
code: 'text/html',
multiline: true,
i18nLabel: 'Body',
i18nDescription: 'Verification_Description',
enableQuery: {
_id: 'Verification_Customized',
value: true,
},
i18nDefaultQuery: {
_id: 'Verification_Customized',
value: false,
},
});
});
});
RocketChat.settings.addGroup('Message', function() {
this.section('Message_Attachments', function() {
this.add('Message_Attachments_GroupAttach', false, {
@ -1604,7 +1194,7 @@ RocketChat.settings.addGroup('Layout', function() {
type: 'string',
public: true,
});
this.add('Layout_Home_Body', '<p>Welcome to Rocket.Chat!</p>\n<p>The Rocket.Chat desktops apps for Windows, macOS and Linux are available to download <a title="Rocket.Chat desktop apps" href="https://rocket.chat/download" target="_blank" rel="noopener">here</a>.</p><p>The native mobile app, Rocket.Chat+,\n for Android and iOS is available from <a title="Rocket.Chat+ on Google Play" href="https://play.google.com/store/apps/details?id=chat.rocket.android" target="_blank" rel="noopener">Google Play</a> and the <a title="Rocket.Chat+ on the App Store" href="https://itunes.apple.com/app/rocket-chat/id1148741252" target="_blank" rel="noopener">App Store</a>.</p>\n<p>For further help, please consult the <a title="Rocket.Chat Documentation" href="https://rocket.chat/docs/" target="_blank" rel="noopener">documentation</a>.</p>\n<p>If you\'re an admin, feel free to change this content via <strong>Administration</strong> -> <strong>Layout</strong> -> <strong>Home Body</strong>. Or clicking <a title="Home Body Layout" href="/admin/Layout">here</a>.</p>', {
this.add('Layout_Home_Body', '<p>Welcome to Rocket.Chat!</p>\n<p>The Rocket.Chat desktops apps for Windows, macOS and Linux are available to download <a title="Rocket.Chat desktop apps" href="https: //rocket.chat/download" target="_blank" rel="noopener">here</a>.</p><p>The native mobile app, Rocket.Chat+,\n for Android and iOS is available from <a title="Rocket.Chat+ on Google Play" href="https: //play.google.com/store/apps/details?id=chat.rocket.android" target="_blank" rel="noopener">Google Play</a> and the <a title="Rocket.Chat+ on the App Store" href="https: //itunes.apple.com/app/rocket-chat/id1148741252" target="_blank" rel="noopener">App Store</a>.</p>\n<p>For further help, please consult the <a title="Rocket.Chat Documentation" href="https: //rocket.chat/docs/" target="_blank" rel="noopener">documentation</a>.</p>\n<p>If you\'re an admin, feel free to change this content via <strong>Administration</strong> -> <strong>Layout</strong> -> <strong>Home Body</strong>. Or clicking <a title="Home Body Layout" href="/admin/Layout">here</a>.</p>', {
type: 'code',
code: 'text/html',
multiline: true,
@ -2937,4 +2527,3 @@ RocketChat.settings.addGroup('Setup_Wizard', function() {
});
RocketChat.settings.init();

@ -0,0 +1,3 @@
import * as Mailer from 'meteor/rocketchat:mailer';
Mailer.setSettings(RocketChat.settings);

@ -1,9 +1,11 @@
/* globals HTTP, emailSettings */
/* globals HTTP */
import _ from 'underscore';
import s from 'underscore.string';
import moment from 'moment';
import dns from 'dns';
import UAParser from 'ua-parser-js';
import * as Mailer from 'meteor/rocketchat:mailer';
import LivechatVisitors from '../models/LivechatVisitors';
import { Analytics } from './Analytics';
@ -54,16 +56,14 @@ RocketChat.Livechat = {
getAgents(department) {
if (department) {
return RocketChat.models.LivechatDepartmentAgents.findByDepartmentId(department);
} else {
return RocketChat.models.Users.findAgents();
}
return RocketChat.models.Users.findAgents();
},
getOnlineAgents(department) {
if (department) {
return RocketChat.models.LivechatDepartmentAgents.getOnlineForDepartment(department);
} else {
return RocketChat.models.Users.findOnlineAgents();
}
return RocketChat.models.Users.findOnlineAgents();
},
getRequiredDepartment(onlineRequired = true) {
const departments = RocketChat.models.LivechatDepartment.findEnabledWithAgents();
@ -750,19 +750,12 @@ RocketChat.Livechat = {
},
sendEmail(from, to, replyTo, subject, html) {
const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
emailSettings = {
Mailer.send({
to,
from,
replyTo,
subject,
html: header + html + footer,
};
Meteor.defer(() => {
Email.send(emailSettings);
html,
});
},

@ -0,0 +1,39 @@
Package.describe({
name: 'rocketchat:mailmessages',
version: '0.0.1',
summary: 'Mailer for Rocket.Chat',
});
Package.onUse(function(api) {
api.use([
'ecmascript',
'ddp-rate-limiter',
'kadira:flow-router',
'rocketchat:lib',
'rocketchat:authorization',
]);
api.use('templating', 'client');
api.addFiles('lib/Mailer.js');
api.addFiles([
'client/startup.js',
'client/router.js',
'client/views/mailer.html',
'client/views/mailer.js',
'client/views/mailerUnsubscribe.html',
'client/views/mailerUnsubscribe.js',
], 'client');
api.addFiles([
'server/startup.js',
'server/models/Users.js',
'server/functions/sendMail.js',
'server/functions/unsubscribe.js',
'server/methods/sendMail.js',
'server/methods/unsubscribe.js',
], 'server');
api.export('Mailer');
});

@ -0,0 +1,61 @@
/* globals */
import s from 'underscore.string';
import * as Mailer from 'meteor/rocketchat:mailer';
Mailer.sendMail = function(from, subject, body, dryrun, query) {
Mailer.checkAddressFormatAndThrow(from, 'Mailer.sendMail');
if (body.indexOf('[unsubscribe]') === -1) {
throw new Meteor.Error('error-missing-unsubscribe-link', 'You must provide the [unsubscribe] link.', {
function: 'Mailer.sendMail',
});
}
let userQuery = { 'mailer.unsubscribed': { $exists: 0 } };
if (query) {
userQuery = { $and: [userQuery, EJSON.parse(query)] };
}
if (dryrun) {
return Meteor.users.find({
'emails.address': from,
}).forEach((user) => {
const email = `${ user.name } <${ user.emails[0].address }>`;
const html = RocketChat.placeholders.replace(body, {
unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', {
_id: user._id,
createdAt: user.createdAt.getTime(),
})),
name: user.name,
email,
});
console.log(`Sending email to ${ email }`);
return Mailer.send({
to: email,
from,
subject,
html,
});
});
}
return Meteor.users.find(userQuery).forEach(function(user) {
const email = `${ user.name } <${ user.emails[0].address }>`;
const html = RocketChat.placeholders.replace(body, {
unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', {
_id: user._id,
createdAt: user.createdAt.getTime(),
})),
name: s.escapeHTML(user.name),
email: s.escapeHTML(email),
});
console.log(`Sending email to ${ email }`);
return Mailer.send({
to: email,
from,
subject,
html,
});
});
};

@ -8,32 +8,7 @@ Package.onUse(function(api) {
api.use([
'ecmascript',
'ddp-rate-limiter',
'kadira:flow-router',
'rocketchat:lib',
'rocketchat:authorization',
]);
api.use('templating', 'client');
api.addFiles('lib/Mailer.js');
api.addFiles([
'client/startup.js',
'client/router.js',
'client/views/mailer.html',
'client/views/mailer.js',
'client/views/mailerUnsubscribe.html',
'client/views/mailerUnsubscribe.js',
], 'client');
api.addFiles([
'server/startup.js',
'server/models/Users.js',
'server/functions/sendMail.js',
'server/functions/unsubscribe.js',
'server/methods/sendMail.js',
'server/methods/unsubscribe.js',
], 'server');
api.export('Mailer');
api.mainModule('server/api.js', 'server');
});

@ -0,0 +1,107 @@
import s from 'underscore.string';
import juice from 'juice';
let contentHeader;
let contentFooter;
let body;
let Settings = {
get: () => {},
};
export const replacekey = (str, key, value = '') => str.replace(new RegExp(`(\\[${ key }\\]|__${ key }__)`, 'igm'), value);
export const translate = (str) => str.replace(/\{ ?([^\} ]+)(( ([^\}]+))+)? ?\}/gmi, (match, key) => TAPi18n.__(key));
export const replace = function replace(str, data = {}) {
if (!str) {
return '';
}
const options = {
Site_Name: Settings.get('Site_Name'),
Site_URL: Settings.get('Site_Url'),
...(data.name && {
fname: s.strLeft(data.name, ' '),
lname: s.strRightBack(data.name, ' '),
}),
...data,
};
return Object.entries(options).reduce((ret, [key, value]) => replacekey(ret, key, value), translate(str));
};
export const replaceEscaped = (str, data = {}) => replace(str, {
Site_Name: s.escapeHTML(RocketChat.settings.get('Site_Name')),
Site_Url: s.escapeHTML(RocketChat.settings.get('Site_Url')),
...Object.entries(data).reduce((ret, [key, value]) => {
ret[key] = s.escapeHTML(value);
return ret;
}, {}),
});
export const wrap = (html, data = {}) => replaceEscaped(body.replace('{{body}}', html), data);
export const inlinecss = (html) => juice.inlineContent(html, Settings.get('email_style'));
export const getTemplate = (template, fn, escape = true) => {
let html = '';
Settings.get(template, (key, value) => {
html = value || '';
fn(escape ? inlinecss(html) : html);
});
Settings.get('email_style', () => {
fn(escape ? inlinecss(html) : html);
});
};
export const getTemplateWrapped = (template, fn) => {
let html = '';
Settings.get('Email_Header', function() {
return html && fn(wrap(html));
});
Settings.get('Email_Footer', function() {
return html && fn(wrap(html));
});
Settings.get(template, (key, value) => {
html = value || '';
return html && fn(wrap(html));
});
Settings.get('email_style', () => html && fn(wrap(html)));
};
export const setSettings = (s) => {
Settings = s;
getTemplate('Email_Header', (value) => {
contentHeader = replace(value || '');
body = inlinecss(`${ contentHeader } {{body}} ${ contentFooter }`);
}, false);
getTemplate('Email_Footer', (value) => {
contentFooter = replace(value || '');
body = inlinecss(`${ contentHeader } {{body}} ${ contentFooter }`);
}, false);
body = inlinecss(`${ contentHeader } {{body}} ${ contentFooter }`);
};
export const rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/;
export const checkAddressFormat = (from) => rfcMailPatternWithName.test(from);
export const sendNoWrap = ({ to, from, subject, html }) => {
if (!checkAddressFormat(to)) {
return;
}
Meteor.defer(() => Email.send({ to, from, subject, html }));
};
export const send = ({ to, from, subject, html, data }) => sendNoWrap({ to, from, subject: replace(subject, data), html: wrap(html, data) });
export const checkAddressFormatAndThrow = (from, func) => {
if (checkAddressFormat(from)) {
return true;
}
throw new Meteor.Error('error-invalid-from-address', 'Invalid from address', {
function: func,
});
};
export const getHeader = () => contentHeader;
export const getFooter = () => contentFooter;

@ -1,82 +0,0 @@
/* globals Mailer */
import s from 'underscore.string';
Mailer.sendMail = function(from, subject, body, dryrun, query) {
const rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/;
if (!rfcMailPatternWithName.test(from)) {
throw new Meteor.Error('error-invalid-from-address', 'Invalid from address', {
function: 'Mailer.sendMail',
});
}
if (body.indexOf('[unsubscribe]') === -1) {
throw new Meteor.Error('error-missing-unsubscribe-link', 'You must provide the [unsubscribe] link.', {
function: 'Mailer.sendMail',
});
}
const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
let userQuery = { 'mailer.unsubscribed': { $exists: 0 } };
if (query) {
userQuery = { $and: [userQuery, EJSON.parse(query)] };
}
if (dryrun) {
return Meteor.users.find({
'emails.address': from,
}).forEach((user) => {
let email = undefined;
if (user.emails && user.emails[0] && user.emails[0].address) {
email = user.emails[0].address;
}
const html = RocketChat.placeholders.replace(body, {
unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', {
_id: user._id,
createdAt: user.createdAt.getTime(),
})),
name: user.name,
email,
});
email = `${ user.name } <${ email }>`;
if (rfcMailPatternWithName.test(email)) {
Meteor.defer(function() {
return Email.send({
to: email,
from,
subject,
html: header + html + footer,
});
});
return console.log(`Sending email to ${ email }`);
}
});
} else {
return Meteor.users.find(userQuery).forEach(function(user) {
let email = undefined;
if (user.emails && user.emails[0] && user.emails[0].address) {
email = user.emails[0].address;
}
const html = RocketChat.placeholders.replace(body, {
unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', {
_id: user._id,
createdAt: user.createdAt.getTime(),
})),
name: s.escapeHTML(user.name),
email: s.escapeHTML(email),
});
email = `${ user.name } <${ email }>`;
if (rfcMailPatternWithName.test(email)) {
Meteor.defer(function() {
return Email.send({
to: email,
from,
subject,
html: header + html + footer,
});
});
return console.log(`Sending email to ${ email }`);
}
});
}
};

@ -7,24 +7,24 @@
// }
import _ from 'underscore';
import * as Mailer from 'meteor/rocketchat:mailer';
RocketChat.smarsh.sendEmail = (data) => {
const attachments = [];
if (data.files.length > 0) {
_.each(data.files, (fileId) => {
const file = RocketChat.models.Uploads.findOneById(fileId);
if (file.store === 'rocketchat_uploads' || file.store === 'fileSystem') {
const rs = UploadFS.getStore(file.store).getReadStream(fileId, file);
attachments.push({
filename: file.name,
streamSource: rs,
});
}
});
}
_.each(data.files, (fileId) => {
const file = RocketChat.models.Uploads.findOneById(fileId);
if (file.store === 'rocketchat_uploads' || file.store === 'fileSystem') {
const rs = UploadFS.getStore(file.store).getReadStream(fileId, file);
attachments.push({
filename: file.name,
streamSource: rs,
});
}
});
Email.send({
Mailer.sendNoWrap({
to: RocketChat.settings.get('Smarsh_Email'),
from: RocketChat.settings.get('From_Email'),
subject: data.subject,

@ -3,6 +3,7 @@
import fs from 'fs';
import path from 'path';
import archiver from 'archiver';
import * as Mailer from 'meteor/rocketchat:mailer';
let zipFolder = '/tmp/zipFiles';
if (RocketChat.settings.get('UserData_FileSystemZipPath') != null) {
@ -285,33 +286,32 @@ const isDownloadFinished = function(exportOperation) {
const sendEmail = function(userId) {
const lastFile = RocketChat.models.UserDataFiles.findLastFileByUser(userId);
if (lastFile) {
const userData = RocketChat.models.Users.findOneById(userId);
if (userData && userData.emails && userData.emails[0] && userData.emails[0].address) {
const emailAddress = `${ userData.name } <${ userData.emails[0].address }>`;
const fromAddress = RocketChat.settings.get('From_Email');
const subject = TAPi18n.__('UserDataDownload_EmailSubject');
const download_link = lastFile.url;
const body = TAPi18n.__('UserDataDownload_EmailBody', { download_link });
const rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/;
if (rfcMailPatternWithName.test(emailAddress)) {
Meteor.defer(function() {
return Email.send({
to: emailAddress,
from: fromAddress,
subject,
html: body,
});
});
if (!lastFile) {
return;
}
const userData = RocketChat.models.Users.findOneById(userId);
return console.log(`Sending email to ${ emailAddress }`);
}
}
if (!userData || userData.emails || userData.emails[0] || userData.emails[0].address) {
return;
}
const emailAddress = `${ userData.name } <${ userData.emails[0].address }>`;
const fromAddress = RocketChat.settings.get('From_Email');
const subject = TAPi18n.__('UserDataDownload_EmailSubject');
const download_link = lastFile.url;
const body = TAPi18n.__('UserDataDownload_EmailBody', { download_link });
if (!Mailer.checkAddressFormat(emailAddress)) {
return;
}
return Mailer.sendNoWrap({
to: emailAddress,
from: fromAddress,
subject,
html: body,
});
};
const makeZipFile = function(exportOperation) {

@ -1,5 +1,6 @@
import _ from 'underscore';
import s from 'underscore.string';
import * as Mailer from 'meteor/rocketchat:mailer';
const accountsConfig = {
forbidClientAccountCreation: true,
@ -21,18 +22,13 @@ Accounts.emailTemplates.userToActivate = {
},
html(options = {}) {
const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
const email = options.reason ? 'Accounts_Admin_Email_Approval_Needed_With_Reason_Default' : 'Accounts_Admin_Email_Approval_Needed_Default';
const html = RocketChat.placeholders.replace(TAPi18n.__(email), {
return Mailer.replace(TAPi18n.__(email), {
name: s.escapeHTML(options.name),
email: s.escapeHTML(options.email),
reason: s.escapeHTML(options.reason),
});
return header + html + footer;
},
};
@ -47,26 +43,30 @@ Accounts.emailTemplates.userActivated = {
},
html({ active, name, username }) {
const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
const activated = username ? 'Activated' : 'Approved';
const action = active ? activated : 'Deactivated';
const html = RocketChat.placeholders.replace(TAPi18n.__(`Accounts_Email_${ action }`), {
return Mailer.replace(TAPi18n.__(`Accounts_Email_${ action }`), {
name: s.escapeHTML(name),
});
return header + html + footer;
},
};
const verifyEmailHtml = Accounts.emailTemplates.verifyEmail.text;
// const verifyEmailHtml = Accounts.emailTemplates.verifyEmail.html;
let verifyEmailTemplate = '';
let enrollAccountTemplate = '';
Meteor.startup(() => {
Mailer.getTemplate('Verification_Email', (value) => {
verifyEmailTemplate = value;
});
Mailer.getTemplate('Accounts_Enrollment_Email', (value) => {
enrollAccountTemplate = value;
});
});
Accounts.emailTemplates.verifyEmail.html = function(user, url) {
url = url.replace(Meteor.absoluteUrl(), `${ Meteor.absoluteUrl() }login/`);
return verifyEmailHtml(user, url);
return Mailer.wrap(Mailer.replacekey(Mailer.replace(verifyEmailTemplate), 'Verification_Url', url));
};
Accounts.urls.resetPassword = function(token) {
@ -75,37 +75,16 @@ Accounts.urls.resetPassword = function(token) {
Accounts.emailTemplates.resetPassword.html = Accounts.emailTemplates.resetPassword.text;
Accounts.emailTemplates.enrollAccount.subject = function(user = {}) {
let subject;
if (RocketChat.settings.get('Accounts_Enrollment_Customized')) {
subject = RocketChat.settings.get('Accounts_Enrollment_Email_Subject');
} else {
subject = TAPi18n.__('Accounts_Enrollment_Email_Subject_Default', {
lng: user.language || RocketChat.settings.get('language') || 'en',
});
}
return RocketChat.placeholders.replace(subject);
Accounts.emailTemplates.enrollAccount.subject = function(user) {
const subject = RocketChat.settings.get('Accounts_Enrollment_Email_Subject');
return Mailer.replace(subject, user);
};
Accounts.emailTemplates.enrollAccount.html = function(user = {}/* , url*/) {
let html;
if (RocketChat.settings.get('Accounts_Enrollment_Customized')) {
html = RocketChat.settings.get('Accounts_Enrollment_Email');
} else {
html = TAPi18n.__('Accounts_Enrollment_Email_Default', {
lng: user.language || RocketChat.settings.get('language') || 'en',
});
}
const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
html = RocketChat.placeholders.replace(html, {
return Mailer.wrap(Mailer.replace(enrollAccountTemplate, {
name: s.escapeHTML(user.name),
email: user.emails && user.emails[0] && s.escapeHTML(user.emails[0].address),
});
return header + html + footer;
}));
};
Accounts.onCreateUser(function(options, user = {}) {
@ -161,7 +140,7 @@ Accounts.onCreateUser(function(options, user = {}) {
html: Accounts.emailTemplates.userToActivate.html(options),
};
Meteor.defer(() => Email.send(email));
Mailer.send(email);
}
return user;

@ -1,5 +1,11 @@
import s from 'underscore.string';
import * as Mailer from 'meteor/rocketchat:mailer';
let verifyEmailTemplate = '';
Meteor.startup(() => {
Mailer.getTemplateWrapped('Verification_Email', (value) => {
verifyEmailTemplate = value;
});
});
Meteor.methods({
registerUser(formData) {
const AllowAnonymousRead = RocketChat.settings.get('Accounts_AllowAnonymousRead');
@ -62,12 +68,11 @@ Meteor.methods({
RocketChat.saveCustomFields(userId, formData);
try {
if (RocketChat.settings.get('Verification_Customized')) {
const subject = RocketChat.placeholders.replace(RocketChat.settings.get('Verification_Email_Subject') || '');
const html = RocketChat.placeholders.replace(RocketChat.settings.get('Verification_Email') || '');
Accounts.emailTemplates.verifyEmail.subject = () => subject;
Accounts.emailTemplates.verifyEmail.html = (userModel, url) => html.replace(/\[Verification_Url]/g, url);
}
const subject = Mailer.replace(RocketChat.settings.get('Verification_Email_Subject'));
Accounts.emailTemplates.verifyEmail.subject = () => subject;
Accounts.emailTemplates.verifyEmail.html = (userModel, url) => Mailer.replace(Mailer.replacekey(verifyEmailTemplate, 'Verification_Url', url), userModel);
Accounts.sendVerificationEmail(userId, userData.email);
} catch (error) {

@ -1,36 +1,45 @@
import * as Mailer from 'meteor/rocketchat:mailer';
let subject = '';
let html = '';
Meteor.startup(() => {
RocketChat.settings.get('Verification_Email_Subject', function(key, value) {
subject = Mailer.replace(value || '');
});
Mailer.getTemplateWrapped('Verification_Email', function(value) {
html = value;
});
});
Meteor.methods({
sendConfirmationEmail(email) {
check(email, String);
email = email.trim();
sendConfirmationEmail(to) {
check(to, String);
const email = to.trim();
const user = RocketChat.models.Users.findOneByEmailAddress(email);
if (user) {
if (RocketChat.settings.get('Verification_Customized')) {
const subject = RocketChat.placeholders.replace(RocketChat.settings.get('Verification_Email_Subject') || '');
const html = RocketChat.placeholders.replace(RocketChat.settings.get('Verification_Email') || '');
Accounts.emailTemplates.verifyEmail.subject = function(/* userModel*/) {
return subject;
};
Accounts.emailTemplates.verifyEmail.html = function(userModel, url) {
return html.replace(/\[Verification_Url]/g, url);
};
}
try {
Accounts.sendVerificationEmail(user._id, email);
} catch (error) {
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${ error.message }`, {
method: 'registerUser',
message: error.message,
});
}
return true;
if (!user) {
return false;
}
Accounts.emailTemplates.verifyEmail.subject = function(/* userModel*/) {
return subject;
};
Accounts.emailTemplates.verifyEmail.html = function(userModel, url) {
return Mailer.replace(html, { Verification_Url:url, name: user.name });
};
try {
return Accounts.sendVerificationEmail(user._id, email);
} catch (error) {
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${ error.message }`, {
method: 'registerUser',
message: error.message,
});
}
return false;
},
});

@ -1,50 +1,56 @@
import s from 'underscore.string';
import * as Mailer from 'meteor/rocketchat:mailer';
let template = '';
Meteor.startup(() => {
Mailer.getTemplateWrapped('Forgot_Password_Email', (value) => {
template = value;
});
});
Meteor.methods({
sendForgotPasswordEmail(email) {
check(email, String);
sendForgotPasswordEmail(to) {
check(to, String);
email = email.trim();
let email = to.trim();
const user = RocketChat.models.Users.findOneByEmailAddress(email);
if (user) {
const regex = new RegExp(`^${ s.escapeRegExp(email) }$`, 'i');
email = (user.emails || []).map((item) => item.address).find((userEmail) => regex.test(userEmail));
if (RocketChat.settings.get('Forgot_Password_Customized')) {
const subject = RocketChat.placeholders.replace(RocketChat.settings.get('Forgot_Password_Email_Subject') || '', {
name: user.name,
email,
});
const html = RocketChat.placeholders.replace(RocketChat.settings.get('Forgot_Password_Email') || '', {
name: s.escapeHTML(user.name),
email: s.escapeHTML(email),
});
Accounts.emailTemplates.from = `${ RocketChat.settings.get('Site_Name') } <${ RocketChat.settings.get('From_Email') }>`;
Accounts.emailTemplates.resetPassword.subject = function(/* userModel*/) {
return subject;
};
Accounts.emailTemplates.resetPassword.html = function(userModel, url) {
return html.replace(/\[Forgot_Password_Url]/g, url);
};
}
try {
Accounts.sendResetPasswordEmail(user._id, email);
} catch (error) {
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${ error.message }`, {
method: 'registerUser',
message: error.message,
});
}
return true;
if (!user) {
return false;
}
return false;
const regex = new RegExp(`^${ s.escapeRegExp(email) }$`, 'i');
email = (user.emails || []).map((item) => item.address).find((userEmail) => regex.test(userEmail));
const subject = Mailer.replace(RocketChat.settings.get('Forgot_Password_Email_Subject') || '', {
name: user.name,
email,
});
const html = Mailer.replace(template, {
name: user.name,
email,
});
Accounts.emailTemplates.from = `${ RocketChat.settings.get('Site_Name') } <${ RocketChat.settings.get('From_Email') }>`;
try {
Accounts.emailTemplates.resetPassword.subject = function(/* userModel*/) {
return subject; // TODO check a better way to do this
};
Accounts.emailTemplates.resetPassword.html = function(userModel, url) {
return Mailer.replacekey(html, 'Forgot_Password_Url', url);
};
return Accounts.sendResetPasswordEmail(user._id, email);
} catch (error) {
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${ error.message }`, {
method: 'registerUser',
message: error.message,
});
}
},
});

@ -1,3 +1,5 @@
import * as Mailer from 'meteor/rocketchat:mailer';
Meteor.methods({
setUserActiveStatus(userId, active) {
check(userId, String);
@ -17,35 +19,36 @@ Meteor.methods({
const user = RocketChat.models.Users.findOneById(userId);
if (user) {
RocketChat.models.Users.setUserActive(userId, active);
if (!user) {
return false;
}
RocketChat.models.Users.setUserActive(userId, active);
if (user.username) {
RocketChat.models.Subscriptions.setArchivedByUsername(user.username, !active);
}
if (active === false) {
RocketChat.models.Users.unsetLoginTokens(userId);
} else {
RocketChat.models.Users.unsetReason(userId);
}
if (user.username) {
RocketChat.models.Subscriptions.setArchivedByUsername(user.username, !active);
}
const destinations = Array.isArray(user.emails) && user.emails.map((email) => `${ user.name || user.username }<${ email.address }>`);
if (active === false) {
RocketChat.models.Users.unsetLoginTokens(userId);
} else {
RocketChat.models.Users.unsetReason(userId);
}
const destinations = Array.isArray(user.emails) && user.emails.map((email) => `${ user.name || user.username }<${ email.address }>`);
const email = {
to: destinations,
from: RocketChat.settings.get('From_Email'),
subject: Accounts.emailTemplates.userActivated.subject({ active }),
html: Accounts.emailTemplates.userActivated.html({ active, name: user.name, username: user.username }),
};
if (destinations) {
const email = {
to: destinations,
from: RocketChat.settings.get('From_Email'),
subject: Accounts.emailTemplates.userActivated.subject({ active }),
html: Accounts.emailTemplates.userActivated.html({ active, name: user.name, username: user.username }),
};
Mailer.sendNoWrap(email);
Meteor.defer(() => Email.send(email));
}
return true;
}
return true;
return false;
},
});

@ -0,0 +1,66 @@
RocketChat.Migrations.add({
version: 134,
up() {
const updateIfDefault = (customized, idEmail, idSubject) => {
const setting = RocketChat.models.Settings.findOne({ _id: customized });
const newSettingEmail = RocketChat.models.Settings.findOne({ _id: idEmail });
const newSettingSubject = RocketChat.models.Settings.findOne({ _id: idSubject });
delete newSettingSubject._id;
delete newSettingSubject.enableQuery;
delete newSettingSubject.i18nDefaultQuery;
delete newSettingEmail._id;
delete newSettingEmail.enableQuery;
delete newSettingEmail.i18nDefaultQuery;
if (setting && (setting.value === false || newSettingEmail.value === '')) {
newSettingEmail.value = newSettingEmail.packageValue;
}
if (setting && (setting.value === false || newSettingSubject.value === '')) {
newSettingSubject.value = newSettingSubject.packageValue;
}
RocketChat.models.Settings.upsert({ _id: idEmail }, newSettingEmail);
RocketChat.models.Settings.upsert({ _id: idSubject }, newSettingSubject);
RocketChat.models.Settings.remove({ _id: customized });
};
const rename = (oldId, newId) => {
const oldSetting = RocketChat.models.Settings.findOne({ _id: oldId });
const newSetting = RocketChat.models.Settings.findOne({ _id: newId });
delete oldSetting._id;
delete oldSetting.enableQuery;
delete oldSetting.i18nDefaultQuery;
oldSetting.packageValue = newSetting.packageValue;
oldSetting.value = newSetting.value || newSetting.packageValue;
RocketChat.models.Settings.upsert({ _id: newId }, oldSetting);
RocketChat.models.Settings.removeById(oldId);
};
updateIfDefault('Accounts_Enrollment_Customized', 'Accounts_Enrollment_Email', 'Accounts_Enrollment_Email_Subject');
updateIfDefault('Accounts_UserAddedEmail_Customized', 'Accounts_UserAddedEmail', 'Accounts_UserAddedEmailSubject');
updateIfDefault('Forgot_Password_Customized', 'Forgot_Password_Email', 'Forgot_Password_Email_Subject');
updateIfDefault('Verification_Customized', 'Verification_Email', 'Verification_Email_Subject');
updateIfDefault('Invitation_Customized', 'Invitation_HTML', 'Invitation_Subject');
rename('Accounts_UserAddedEmail', 'Accounts_UserAddedEmail_Email');
rename('Accounts_UserAddedEmailSubject', 'Accounts_UserAddedEmail_Subject');
rename('Invitation_HTML', 'Invitation_Email');
Object.entries({
Email_Header: '<html><table border="0" cellspacing="0" cellpadding="0" width="100%" bgcolor="#f3f3f3" style="color:#4a4a4a;font-family: Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;border-collapse:collapse;border-spacing:0;margin:0 auto"><tr><td style="padding:1em"><table border="0" cellspacing="0" cellpadding="0" align="center" width="100%" style="width:100%;margin:0 auto;max-width:800px"><tr><td bgcolor="#ffffff" style="background-color:#ffffff; border: 1px solid #DDD; font-size: 10pt; font-family: Helvetica,Arial,sans-serif;"><table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td style="background-color: #04436a;"><h1 style="font-family: Helvetica,Arial,sans-serif; padding: 0 1em; margin: 0; line-height: 70px; color: #FFF;">[Site_Name]</h1></td></tr><tr><td style="padding: 1em; font-size: 10pt; font-family: Helvetica,Arial,sans-serif;">',
Email_Footer: '</td></tr></table></td></tr><tr><td border="0" cellspacing="0" cellpadding="0" width="100%" style="font-family: Helvetica,Arial,sans-serif; max-width: 800px; margin: 0 auto; padding: 1.5em; text-align: center; font-size: 8pt; color: #999;">Powered by <a href="https://rocket.chat" target="_blank">Rocket.Chat</a></td></tr></table></td></tr></table></html>',
Email_Footer_Direct_Reply: '</td></tr></table></td></tr><tr><td border="0" cellspacing="0" cellpadding="0" width="100%" style="font-family: Helvetica,Arial,sans-serif; max-width: 800px; margin: 0 auto; padding: 1.5em; text-align: center; font-size: 8pt; color: #999;">You can directly reply to this email.<br>Do not modify previous emails in the thread.<br>Powered by <a href="https://rocket.chat" target="_blank">Rocket.Chat</a></td></tr></table></td></tr></table></html>',
}).forEach(([_id, oldValue]) => {
const setting = RocketChat.models.Settings.findOne({ _id });
if (setting.value === oldValue) {
RocketChat.models.Settings.updateValueById(_id, setting.packageValue);
}
});
},
});
Loading…
Cancel
Save