Files
speckle-server/packages/server/modules/serverinvites/services/coreEmailContents.ts
T
andrewwallacespeckle d1f040051d refactor(server): Update system emails (#4074)
* Update email verification. And workspace invite emails.

* Footer logos

* Workspace requests

* Trial

* Misc emails

* New logo

* Updates to font, spacing and footer

* Remove space

* Update invite emails

* Correct classes
2025-03-07 15:30:03 +00:00

207 lines
6.0 KiB
TypeScript

import { GetStream } from '@/modules/core/domain/streams/operations'
import {
getRegistrationRoute,
getStreamRoute
} from '@/modules/core/helpers/routeHelper'
import { StreamRecord } from '@/modules/core/helpers/types'
import { EmailTemplateParams } from '@/modules/emails/domain/operations'
import { sanitizeMessage } from '@/modules/emails/services/emailRendering'
import {
PrimaryInviteResourceTarget,
ProjectInviteResourceTarget
} from '@/modules/serverinvites/domain/types'
import { InviteCreateValidationError } from '@/modules/serverinvites/errors'
import {
isProjectResourceTarget,
isServerResourceTarget
} from '@/modules/serverinvites/helpers/core'
import { BuildInviteEmailContents } from '@/modules/serverinvites/services/operations'
import { getFrontendOrigin } from '@/modules/shared/helpers/envHelper'
function buildServerMjmlPreamble(params: Parameters<BuildInviteEmailContents>[0]) {
const { invite, serverInfo, inviter } = params
const { message } = invite
const bodyStart = `
<mj-text align="center" line-height="2">
Hello!
<br />
<br />
${inviter.name} has just sent you this invitation to join the <b>${
serverInfo.name
}</b> Speckle Server!
${message ? inviter.name + ' said: <em>"' + message + '"</em>' : ''}
</mj-text>
`
return {
bodyStart,
bodyEnd:
'<mj-text align="center" line-height="2">Feel free to ignore this invite if you do not know the person sending it.</mj-text>'
}
}
function buildServerTextPreamble(params: Parameters<BuildInviteEmailContents>[0]) {
const { invite, serverInfo, inviter } = params
const { message } = invite
const bodyStart = `Hello!
${inviter.name} has just sent you this invitation to join the ${
serverInfo.name
} Speckle Server!
${message ? inviter.name + ' said: "' + sanitizeMessage(message, true) + '"' : ''}`
return {
bodyStart,
bodyEnd: 'Feel free to ignore this invite if you do not know the person sending it.'
}
}
const buildServerEmailTemplateParams = (
params: Parameters<BuildInviteEmailContents>[0]
): EmailTemplateParams => {
const { invite } = params
const inviteLink = new URL(
`${getRegistrationRoute()}?token=${invite.token}`,
getFrontendOrigin()
).toString()
return {
mjml: buildServerMjmlPreamble(params),
text: buildServerTextPreamble(params),
cta: {
title: 'Accept the invitation',
url: inviteLink
}
}
}
const buildServerInviteContentsFactory = (): BuildInviteEmailContents => (params) => {
const { inviter } = params
const subject = 'Speckle Invitation from ' + inviter.name
return {
emailParams: buildServerEmailTemplateParams(params),
subject
}
}
type BuildProjectInviteEmailContents = (
params: Parameters<BuildInviteEmailContents>[0] & {
primaryResourceTarget: PrimaryInviteResourceTarget<ProjectInviteResourceTarget>
}
) => ReturnType<BuildInviteEmailContents>
function buildProjectMjmlPreamble(
params: Parameters<BuildProjectInviteEmailContents>[0] & { project: StreamRecord }
) {
const { invite, inviter, project } = params
const { message } = invite
const bodyStart = `
<mj-text align="center" line-height="2" >
Hello!
<br />
<br />
${
inviter.name
} has just sent you this invitation to become a collaborator on the <b>${
project.name
}</b> project!
${message ? inviter.name + ' said: <em>"' + message + '"</em>' : ''}
</mj-text>
`
return {
bodyStart,
bodyEnd:
'<mj-text align="center" line-height="2" >Feel free to ignore this invite if you do not know the person sending it.</mj-text>'
}
}
function buildProjectTextPreamble(
params: Parameters<BuildProjectInviteEmailContents>[0] & { project: StreamRecord }
) {
const { invite, project, inviter } = params
const { message } = invite
const bodyStart = `Hello!
${inviter.name} has just sent you this invitation to become a collaborator on the "${
project.name
}" project!
${message ? inviter.name + ' said: "' + sanitizeMessage(message, true) + '"' : ''}`
return {
bodyStart,
bodyEnd: 'Feel free to ignore this invite if you do not know the person sending it.'
}
}
const buildProjectEmailTemplateParams = (
params: Parameters<BuildProjectInviteEmailContents>[0] & { project: StreamRecord }
): EmailTemplateParams => {
const { invite, project } = params
const inviteLink = new URL(
`${getStreamRoute(project.id)}?token=${invite.token}&accept=true`,
getFrontendOrigin()
).toString()
return {
mjml: buildProjectMjmlPreamble(params),
text: buildProjectTextPreamble(params),
cta: {
title: 'Accept the invitation',
url: inviteLink
}
}
}
type BuildProjectInviteContentsFactoryDeps = { getStream: GetStream }
const buildProjectInviteContentsFactory =
(deps: BuildProjectInviteContentsFactoryDeps): BuildProjectInviteEmailContents =>
async (params) => {
const { getStream } = deps
const { inviter, primaryResourceTarget } = params
const project = await getStream({ streamId: primaryResourceTarget.resourceId })
if (!project) {
throw new InviteCreateValidationError(
'Attempting to invite into a non-existant project'
)
}
const subject = `${inviter.name} wants to share the project "${project.name}" on Speckle with you`
return {
emailParams: buildProjectEmailTemplateParams({ ...params, project }),
subject
}
}
export type BuildInviteContentsFactoryDeps = BuildProjectInviteContentsFactoryDeps
export const buildCoreInviteEmailContentsFactory =
(deps: BuildProjectInviteContentsFactoryDeps): BuildInviteEmailContents =>
(params) => {
const { invite } = params
const primaryResourceTarget = invite.resource
if (isServerResourceTarget(primaryResourceTarget)) {
return buildServerInviteContentsFactory()(params)
} else if (isProjectResourceTarget(primaryResourceTarget)) {
return buildProjectInviteContentsFactory(deps)({
...params,
primaryResourceTarget
})
} else {
throw new InviteCreateValidationError(
'Unexpected resource target type: ' + primaryResourceTarget.resourceType
)
}
}