diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml
index 6d9f64c07e..d04fd7c0e4 100644
--- a/hosting/docker-compose.yaml
+++ b/hosting/docker-compose.yaml
@@ -16,7 +16,7 @@ services:
MINIO_URL: http://minio-service:9000
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
- HOSTING_KEY: ${HOSTING_KEY}
+ INTERNAL_KEY: ${INTERNAL_KEY}
BUDIBASE_ENVIRONMENT: ${BUDIBASE_ENVIRONMENT}
PORT: 4002
JWT_SECRET: ${JWT_SECRET}
@@ -44,7 +44,7 @@ services:
COUCH_DB_USERNAME: ${COUCH_DB_USER}
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
- SELF_HOST_KEY: ${HOSTING_KEY}
+ INTERNAL_KEY: ${INTERNAL_KEY}
REDIS_URL: redis-service:6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
depends_on:
diff --git a/hosting/hosting.properties b/hosting/hosting.properties
index 4297ec60a1..6ad962c493 100644
--- a/hosting/hosting.properties
+++ b/hosting/hosting.properties
@@ -1,10 +1,6 @@
# Use the main port in the builder for your self hosting URL, e.g. localhost:10000
MAIN_PORT=10000
-# Use this password when configuring your self hosting settings
-# This should be updated
-HOSTING_KEY=budibase
-
# This section contains all secrets pertaining to the system
# These should be updated
JWT_SECRET=testsecret
@@ -13,6 +9,7 @@ MINIO_SECRET_KEY=budibase
COUCH_DB_PASSWORD=budibase
COUCH_DB_USER=budibase
REDIS_PASSWORD=budibase
+INTERNAL_KEY=budibase
# This section contains variables that do not need to be altered under normal circumstances
APP_PORT=4002
diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js
index 0f40fd6aae..983e87854a 100644
--- a/packages/server/src/automations/actions.js
+++ b/packages/server/src/automations/actions.js
@@ -1,4 +1,5 @@
-const sendEmail = require("./steps/sendgridEmail")
+const sendgridEmail = require("./steps/sendgridEmail")
+const sendSmtpEmail = require("./steps/sendSmtpEmail")
const createRow = require("./steps/createRow")
const updateRow = require("./steps/updateRow")
const deleteRow = require("./steps/deleteRow")
@@ -14,7 +15,8 @@ const {
} = require("../utilities/fileSystem")
const BUILTIN_ACTIONS = {
- SEND_EMAIL: sendEmail.run,
+ SEND_EMAIL: sendgridEmail.run,
+ SEND_EMAIL_SMTP: sendSmtpEmail.run,
CREATE_ROW: createRow.run,
UPDATE_ROW: updateRow.run,
DELETE_ROW: deleteRow.run,
@@ -24,7 +26,8 @@ const BUILTIN_ACTIONS = {
EXECUTE_QUERY: executeQuery.run,
}
const BUILTIN_DEFINITIONS = {
- SEND_EMAIL: sendEmail.definition,
+ SEND_EMAIL: sendgridEmail.definition,
+ SEND_EMAIL_SMTP: sendSmtpEmail.definition,
CREATE_ROW: createRow.definition,
UPDATE_ROW: updateRow.definition,
DELETE_ROW: deleteRow.definition,
diff --git a/packages/server/src/automations/steps/sendSmtpEmail.js b/packages/server/src/automations/steps/sendSmtpEmail.js
index e69de29bb2..764972b402 100644
--- a/packages/server/src/automations/steps/sendSmtpEmail.js
+++ b/packages/server/src/automations/steps/sendSmtpEmail.js
@@ -0,0 +1,67 @@
+const { sendSmtpEmail } = require("../../utilities/workerRequests")
+
+module.exports.definition = {
+ description: "Send an email using SMTP",
+ tagline: "Send SMTP email to {{inputs.to}}",
+ icon: "ri-mail-open-line",
+ name: "Send Email (SMTP)",
+ type: "ACTION",
+ stepId: "SEND_EMAIL_SMTP",
+ inputs: {},
+ schema: {
+ inputs: {
+ properties: {
+ to: {
+ type: "string",
+ title: "Send To",
+ },
+ from: {
+ type: "string",
+ title: "Send From",
+ },
+ subject: {
+ type: "string",
+ title: "Email Subject",
+ },
+ contents: {
+ type: "string",
+ title: "HTML Contents",
+ },
+ },
+ required: ["to", "from", "subject", "contents"],
+ },
+ outputs: {
+ properties: {
+ success: {
+ type: "boolean",
+ description: "Whether the email was sent",
+ },
+ response: {
+ type: "object",
+ description: "A response from the email client, this may be an error",
+ },
+ },
+ required: ["success"],
+ },
+ },
+}
+
+module.exports.run = async function ({ inputs }) {
+ let { to, from, subject, contents } = inputs
+ if (!contents) {
+ contents = "
No content
"
+ }
+ try {
+ let response = await sendSmtpEmail(to, from, subject, contents)
+ return {
+ success: true,
+ response,
+ }
+ } catch (err) {
+ console.error(err)
+ return {
+ success: false,
+ response: err,
+ }
+ }
+}
diff --git a/packages/server/src/automations/steps/sendgridEmail.js b/packages/server/src/automations/steps/sendgridEmail.js
index 26c404257e..5485116e89 100644
--- a/packages/server/src/automations/steps/sendgridEmail.js
+++ b/packages/server/src/automations/steps/sendgridEmail.js
@@ -1,5 +1,5 @@
module.exports.definition = {
- description: "Send an email",
+ description: "Send an email using SendGrid",
tagline: "Send email to {{inputs.to}}",
icon: "ri-mail-open-line",
name: "Send Email (SendGrid)",
diff --git a/packages/server/src/utilities/workerRequests.js b/packages/server/src/utilities/workerRequests.js
index 6bf3bf6461..8d4b1edad2 100644
--- a/packages/server/src/utilities/workerRequests.js
+++ b/packages/server/src/utilities/workerRequests.js
@@ -34,17 +34,29 @@ function request(ctx, request) {
exports.request = request
-exports.sendSmtpEmail = async (to, from, contents) => {
+exports.sendSmtpEmail = async (to, from, subject, contents) => {
const response = await fetch(
- checkSlashesInUrl(env.WORKER_URL + `/api/`),
+ checkSlashesInUrl(env.WORKER_URL + `/api/admin/email/send`),
request(null, {
method: "POST",
headers: {
"x-budibase-api-key": env.INTERNAL_KEY,
},
- body: {},
+ body: {
+ email: to,
+ from,
+ contents,
+ subject,
+ purpose: "custom",
+ },
})
)
+
+ const json = await response.json()
+ if (json.status !== 200 && response.status !== 200) {
+ throw "Unable to send email."
+ }
+ return json
}
exports.getDeployedApps = async ctx => {
diff --git a/packages/worker/src/api/controllers/admin/auth.js b/packages/worker/src/api/controllers/admin/auth.js
index 598e43e8ad..30c7a264bd 100644
--- a/packages/worker/src/api/controllers/admin/auth.js
+++ b/packages/worker/src/api/controllers/admin/auth.js
@@ -53,8 +53,9 @@ exports.reset = async ctx => {
)
}
try {
+
const user = await getGlobalUserByEmail(email)
- await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, { user })
+ await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, { user, subject: "{{ company }} platform password reset" })
} catch (err) {
// don't throw any kind of error to the user, this might give away something
}
diff --git a/packages/worker/src/api/controllers/admin/email.js b/packages/worker/src/api/controllers/admin/email.js
index 04e85e7f44..9bfb281b9a 100644
--- a/packages/worker/src/api/controllers/admin/email.js
+++ b/packages/worker/src/api/controllers/admin/email.js
@@ -5,13 +5,13 @@ const authPkg = require("@budibase/auth")
const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name
exports.sendEmail = async ctx => {
- const { groupId, email, userId, purpose } = ctx.request.body
+ const { groupId, email, userId, purpose, contents, from, subject } = ctx.request.body
let user
if (userId) {
const db = new CouchDB(GLOBAL_DB)
user = await db.get(userId)
}
- const response = await sendEmail(email, purpose, { groupId, user })
+ const response = await sendEmail(email, purpose, { groupId, user, contents, from, subject })
ctx.body = {
...response,
message: `Email sent to ${email}.`,
diff --git a/packages/worker/src/api/controllers/admin/users.js b/packages/worker/src/api/controllers/admin/users.js
index 5c35c65e27..c6507b690a 100644
--- a/packages/worker/src/api/controllers/admin/users.js
+++ b/packages/worker/src/api/controllers/admin/users.js
@@ -136,7 +136,7 @@ exports.invite = async ctx => {
if (existing) {
ctx.throw(400, "Email address already in use.")
}
- await sendEmail(email, EmailTemplatePurpose.INVITATION)
+ await sendEmail(email, EmailTemplatePurpose.INVITATION, { subject: "{{ company }} platform invitation" })
ctx.body = {
message: "Invitation has been sent.",
}
diff --git a/packages/worker/src/api/routes/admin/email.js b/packages/worker/src/api/routes/admin/email.js
index 66079c5fbb..d3d0d4faae 100644
--- a/packages/worker/src/api/routes/admin/email.js
+++ b/packages/worker/src/api/routes/admin/email.js
@@ -10,8 +10,11 @@ function buildEmailSendValidation() {
// prettier-ignore
return joiValidator.body(Joi.object({
email: Joi.string().email(),
+ purpose: Joi.string().valid(...Object.values(EmailTemplatePurpose)),
groupId: Joi.string().allow("", null),
- purpose: Joi.string().allow(...Object.values(EmailTemplatePurpose)),
+ fromt: Joi.string().allow("", null),
+ contents: Joi.string().allow("", null),
+ subject: Joi.string().allow("", null),
}).required().unknown(true))
}
diff --git a/packages/worker/src/utilities/email.js b/packages/worker/src/utilities/email.js
index 7a2069bbb4..6a99863341 100644
--- a/packages/worker/src/utilities/email.js
+++ b/packages/worker/src/utilities/email.js
@@ -7,6 +7,7 @@ const { getSettingsTemplateContext } = require("./templates")
const { processString } = require("@budibase/string-templates")
const { getResetPasswordCode, getInviteCode } = require("../utilities/redis")
+const TEST_MODE = false
const GLOBAL_DB = StaticDatabases.GLOBAL.name
const TYPE = TemplateTypes.EMAIL
@@ -14,18 +15,32 @@ const FULL_EMAIL_PURPOSES = [
EmailTemplatePurpose.INVITATION,
EmailTemplatePurpose.PASSWORD_RECOVERY,
EmailTemplatePurpose.WELCOME,
+ EmailTemplatePurpose.CUSTOM,
]
function createSMTPTransport(config) {
- const options = {
- port: config.port,
- host: config.host,
- secure: config.secure || false,
- auth: config.auth,
- }
- if (config.selfSigned) {
- options.tls = {
- rejectUnauthorized: false,
+ let options
+ if (!TEST_MODE) {
+ options = {
+ port: config.port,
+ host: config.host,
+ secure: config.secure || false,
+ auth: config.auth,
+ }
+ if (config.selfSigned) {
+ options.tls = {
+ rejectUnauthorized: false,
+ }
+ }
+ } else {
+ options = {
+ port: 587,
+ host: "smtp.ethereal.email",
+ secure: false,
+ auth: {
+ user: "don.bahringer@ethereal.email",
+ pass: "yCKSH8rWyUPbnhGYk9",
+ },
}
}
return nodemailer.createTransport(options)
@@ -46,10 +61,12 @@ async function getLinkCode(purpose, email, user) {
* Builds an email using handlebars and the templates found in the system (default or otherwise).
* @param {string} purpose the purpose of the email being built, e.g. invitation, password reset.
* @param {string} email the address which it is being sent to for contextual purposes.
- * @param {object|null} user If being sent to an existing user then the object can be provided for context.
+ * @param {object} context the context which is being used for building the email (hbs context).
+ * @param {object|null} user if being sent to an existing user then the object can be provided for context.
+ * @param {string|null} contents if using a custom template can supply contents for context.
* @return {Promise} returns the built email HTML if all provided parameters were valid.
*/
-async function buildEmail(purpose, email, user) {
+async function buildEmail(purpose, email, context, { user, contents } = {}) {
// this isn't a full email
if (FULL_EMAIL_PURPOSES.indexOf(purpose) === -1) {
throw `Unable to build an email of type ${purpose}`
@@ -63,11 +80,9 @@ async function buildEmail(purpose, email, user) {
}
base = base.contents
body = body.contents
-
- // if there is a link code needed this will retrieve it
- const code = await getLinkCode(purpose, email, user)
- const context = {
- ...(await getSettingsTemplateContext(purpose, code)),
+ context = {
+ ...context,
+ contents,
email,
user: user || {},
}
@@ -116,27 +131,35 @@ exports.isEmailConfigured = async (groupId = null) => {
* @param {object|undefined} user If sending to an existing user the object can be provided, this is used in the context.
* @param {string|undefined} from If sending from an address that is not what is configured in the SMTP config.
* @param {string|undefined} contents If sending a custom email then can supply contents which will be added to it.
+ * @param {string|undefined} subject A custom subject can be specified if the config one is not desired.
* @return {Promise