diff --git a/README.md b/README.md index fc2ed27d08..272706c929 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ services: - EMAIL_SERVER_PORT=587 - EMAIL_SERVER_AUTH_USER=mail_user - EMAIL_SERVER_AUTH_PASSWORD=mail_password + - ADMIN_EMAIL=mail@example.com # the sender address to send out emails ports: - "3000:3000" networks: diff --git a/package-lock.json b/package-lock.json index f1d4c2a658..7ddae9c5b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "morgan": "^1.10.0", "nconf": "^0.12.1", "node-gcm": "^1.0.5", + "nodemailer": "^6.9.15", "on-headers": "^1.0.2", "passport": "^0.5.3", "passport-facebook": "^3.0.0", @@ -16010,6 +16011,15 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, + "node_modules/nodemailer": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz", + "integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/noop-logger": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", diff --git a/package.json b/package.json index 039ca8e7d9..27b1f28921 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "morgan": "^1.10.0", "nconf": "^0.12.1", "node-gcm": "^1.0.5", + "nodemailer": "^6.9.15", "on-headers": "^1.0.2", "passport": "^0.5.3", "passport-facebook": "^3.0.0", diff --git a/website/server/libs/email.js b/website/server/libs/email.js index ea48c39c30..7a77f89402 100644 --- a/website/server/libs/email.js +++ b/website/server/libs/email.js @@ -4,6 +4,7 @@ import { TAVERN_ID } from '../models/group'; // eslint-disable-line import/no-cy import { encrypt } from './encryption'; import logger from './logger'; import common from '../../common'; +import sendEmail from './emailSmtp'; const IS_PROD = nconf.get('IS_PROD'); const EMAIL_SERVER = { @@ -161,6 +162,8 @@ export async function sendTxn (mailingInfoArray, emailType, variables, personalV return null; } + return sendEmail(emailType, variables, personalVariables); + return got.post(`${EMAIL_SERVER.url}/job`, { retry: 5, // retry the http request to the email server 5 times timeout: 60000, // wait up to 60s before timing out diff --git a/website/server/libs/emailSmtp.js b/website/server/libs/emailSmtp.js new file mode 100644 index 0000000000..2368cfa61f --- /dev/null +++ b/website/server/libs/emailSmtp.js @@ -0,0 +1,553 @@ +import nconf from 'nconf'; +import nodemailer from 'nodemailer'; +import logger from './logger'; + +let transporter = nodemailer.createTransport({ + host: nconf.get('EMAIL_SERVER_URL'), + port: nconf.get('EMAIL_SERVER_PORT') || 587, + secure: false, + auth: { + user: nconf.get('EMAIL_SERVER_AUTH_USER'), + pass: nconf.get('EMAIL_SERVER_AUTH_PASSWORD'), + }, +}); + +const adminMail = nconf.get('ADMIN_EMAIL'); + +let templates = { + 'reset-password': { + subject: "Password Reset for Habitica", + text: (variables) => `We received a request to have your Habitica password reset. To get started, open the link below. This link is only valid for 24 hours.\n\n${variables['PASSWORD_RESET_LINK']}`, + button_text: "Reset Password", + button_link: (variables) => variables['PASSWORD_RESET_LINK'], + text_before: () => `We received a request to have your Habitica password reset. To get started, open the link below. This link is only valid for 24 hours. If you did not make this request, please ignore this email.` + + }, + 'new-pm': { + subject: "You Received a Private Message", + text: (variables) => `You've just received a private message from ${variables['SENDER']} on Habitica. Head to the site to read your message: ${variables['BASE_URL']}`, + button_text: "Read Message", + button_link: (variables) => variables['BASE_URL'], + text_before: (variables) => `You've just received a private message from ${variables['SENDER']} on Habitica.` + }, + 'welcome-v2b': { + subject: "Welcome to Habitica", + text: (variables) => `Welcome! To get started simply head over to ${variables['BASE_URL']}.`, + button_text: "Get Started", + button_link: (variables) => variables['BASE_URL'], + text_before: () => `Welcome! To get started simply head over to Habitica.` + }, + 'invited-party': { + subject: "You Were Invited to Join a Party on Habitica", + text: (variables) => `${variables['INVITER']} invited you to the party ${variables['PARTY_NAME']}. To join, visit the website: ${variables['BASE_URL']}${variables['PARTY_URL']}`, + button_text: "Join Party", + button_link: (variables) => `${variables['BASE_URL']}${variables['PARTY_URL']}`, + text_before: (variables) => `${variables['INVITER']} invited you to the party ${variables['PARTY_NAME']}.` + }, + 'invited-guild': { + subject: "You Were Invited to Join a Guild on Habitica", + text: (variables) => `You have an invitation to the guild ${variables['GUILD_NAME']}. To join, visit the website: ${variables['BASE_URL']}${variables['GUILD_URL']}`, + button_text: "Join Guild", + button_link: (variables) => `${variables['BASE_URL']}${variables['GUILD_URL']}`, + text_before: (variables) => `You have an invitation to the guild ${variables['GUILD_NAME']}.` + }, + 'group-member-join': { + subject: "You Were Invited to Join a Group on Habitica", + text: (variables) => `${variables['LEADER']} invited you to the group ${variables['GROUP_NAME']}. To join, visit the website: ${variables['BASE_URL']}`, + button_text: "Join Group", + button_link: (variables) => `${variables['BASE_URL']}`, + text_before: (variables) => `${variables['LEADER']} invited you to the group ${variables['GROUP_NAME']}.` + }, + 'invite-friend': { + subject: "You Were Invited to Join Habitica", + text: (variables) => `${variables['INVITER']} invited you to join them on Habitica. To join, visit the website: ${variables['BASE_URL']}${variables['LINK']}`, + button_text: "Get Started", + button_link: (variables) => `${variables['BASE_URL']}${variables['LINK']}`, + text_before: (variables) => `${variables['INVITER']} invited you to join them on Habitica.` + }, + 'invite-friend-guild': { + subject: "You Were Invited to Join a Guild on Habitica", + text: (variables) => `You have an invitation to the guild ${variables['GUILD_NAME']}. To join, visit the website: ${variables['BASE_URL']}${variables['LINK']}`, + button_text: "Join Guild", + button_link: (variables) => `${variables['BASE_URL']}${variables['LINK']}`, + text_before: (variables) => `You have an invitation to the guild ${variables['GUILD_NAME']}.` + }, + 'invite-boss-quest': { + subject: "New Boss Quest on Habitica", + text: (variables) => `${variables['INVITER']} invited you to the quest '${variables['QUEST_NAME']}'. Head over to the party to join: ${variables['BASE_URL']}${variables['PARTY_URL']}`, + button_text: "Visit Party", + button_link: (variables) => `${variables['BASE_URL']}${variables['PARTY_URL']}`, + text_before: (variables) => `${variables['INVITER']} invited you to the quest '${variables['QUEST_NAME']}'. Head over to the party to join the quest.` + }, + 'invite-collection-quest': { + subject: "New Collection Quest on Habitica", + text: (variables) => `${variables['INVITER']} invited you to the quest '${variables['QUEST_NAME']}'. Head over to the party to join: ${variables['BASE_URL']}${variables['PARTY_URL']}`, + button_text: "Visit Party", + button_link: (variables) => `${variables['BASE_URL']}${variables['PARTY_URL']}`, + text_before: (variables) => `${variables['INVITER']} invited you to the quest '${variables['QUEST_NAME']}'. Head over to the party to join the quest.` + }, + 'quest-started': { + subject: "Quest Started on Habitica", + text: () => `The quest you have joined just started.`, + button_text: "Visit Habitica", + button_link: (variables) => variables['BASE_URL'], + text_before: () => `The quest you have joined just started.` + }, + 'party-invite-rescinded': { + subject: "Your Party Invite was Rescinded", + text: (variables) => `You are no longer invited to join the party ${variables['GROUP_NAME']}.`, + button_text: "Visit Habitica", + button_link: (variables) => variables['BASE_URL'], + text_before: (variables) => `You are no longer invited to join the party ${variables['GROUP_NAME']}.` + }, + 'kicked-from-party': { + subject: "Removed from Party", + text: (variables) => `You were removed from your party.` + variables['MESSAGE'] ? `\n\nMessage from the party leader:\n${variables['MESSAGE']}` : ``, + button_text: "Visit Habitica", + button_link: (variables) => variables['BASE_URL'], + text_before: (variables) => `You were removed from your party.` + variables['MESSAGE'] ? `

Message from the party leader:
${variables['MESSAGE']}` : ``, + }, + 'kicked-from-guild': { + subject: "Removed from Guild", + text: (variables) => `You were removed from the guild ${variables['GROUP_NAME']}.` + variables['MESSAGE'] ? `\n\nMessage from the party leader:\n${variables['MESSAGE']}` : ``, + button_text: "Visit Habitica", + button_link: (variables) => variables['BASE_URL'], + text_before: (variables) => `You were removed from the guild ${variables['GROUP_NAME']}.` + variables['MESSAGE'] ? `

Message from the party leader:
${variables['MESSAGE']}` : ``, + } +}; + + +let htmlTemplate = (variables, button_text, button_link, text_before) => ` + + + + + + Habitica + + + + + + + + + + +
+ +
+ + + + + + +
+
+

Greetings${variables['RECIPIENT_NAME'] ? ` ${variables['RECIPIENT_NAME']}` : ''},

+

${text_before}

+ + + + + + + + +
+ + + + + + +
${button_text}
+
+ + +

Warmly,
The Habitica Team

+
+
+ + + + +
+ +
+ + +` + + +export default function sendEmail(emailType, variables, personalVariables) { + let variablesMap = {}; + variables.forEach(variable => variablesMap[variable.name] = variable.content); + + let template = templates[emailType]; + if (!template) { + logger.error(new Error(`Could not find email template for '${emailType}'. Won't be able to send the email.`)); + return + } + + personalVariables.forEach(recipient => { + let personalVariablesMap = variablesMap + recipient['vars'].forEach(variable => personalVariablesMap[variable.name] = variable.content); + + transporter.sendMail({ + from: "Habitica <" + adminMail + ">", + to: `${personalVariablesMap['RECIPIENT_NAME']} <${recipient.rcpt}>`, + subject: template.subject, + text: `Greetings${personalVariablesMap['RECIPIENT_NAME'] ? ` ${personalVariablesMap['RECIPIENT_NAME']}` : ''},\n\n` + template.text(personalVariablesMap) + "\n\nWarmly,\nThe Habitica Team", + html: htmlTemplate(personalVariablesMap, template.button_text, template.button_link(personalVariablesMap), template.text_before(personalVariablesMap)) + }) + }) +} \ No newline at end of file