diff --git a/apps/api/src/mail/emails/components/base-email-template.tsx b/apps/api/src/mail/emails/components/base-email-template.tsx
new file mode 100644
index 00000000..891ddfe0
--- /dev/null
+++ b/apps/api/src/mail/emails/components/base-email-template.tsx
@@ -0,0 +1,84 @@
+import * as React from 'react'
+import {
+ Body,
+ Container,
+ Head,
+ Heading,
+ Html,
+ Link,
+ Preview,
+ Section,
+ Text
+} from '@react-email/components'
+import {
+ container,
+ content,
+ footer,
+ footerText,
+ h1,
+ link,
+ main,
+ text
+} from '../styles/common-styles'
+
+interface BaseEmailTemplateProps {
+ previewText: string
+ heading: string
+ children: React.ReactNode
+}
+
+export const BaseEmailTemplate: React.FC = ({
+ previewText,
+ heading,
+ children
+}) => {
+ return (
+
+
+ {previewText}
+
+
+
+ {heading}
+ {children}
+
+ If you believe this action was taken in error or have any
+ questions regarding this change, please contact your project
+ administrator or our support team.
+
+
+ We appreciate your understanding and thank you for your
+ contributions to the project.
+
+
+ Cheers,
+
+ Team Keyshade
+
+
+
+
+ This is an automated message. Please do not reply to this email.
+
+
+ Read our{' '}
+
+ Privacy Policy
+ {' '}
+ and{' '}
+
+ Terms and Conditions
+ {' '}
+ for more information on how we manage your data and services.
+
+
+
+
+
+ )
+}
+
+export default BaseEmailTemplate
\ No newline at end of file
diff --git a/apps/api/src/mail/emails/styles/common-styles.ts b/apps/api/src/mail/emails/styles/common-styles.ts
new file mode 100644
index 00000000..027deedb
--- /dev/null
+++ b/apps/api/src/mail/emails/styles/common-styles.ts
@@ -0,0 +1,78 @@
+import { CSSProperties } from 'react'
+
+export const main: CSSProperties = {
+ fontFamily: "'Segoe UI', 'Roboto', sans-serif",
+ lineHeight: '1.6',
+ color: '#04050a',
+ backgroundColor: '#fafafa',
+ margin: '0',
+ padding: '20px'
+}
+
+export const container: CSSProperties = {
+ maxWidth: '600px',
+ margin: '0 auto',
+ backgroundColor: '#fff',
+ borderRadius: '5px',
+ boxShadow: '0 2px 8px rgba(0, 0, 0, 0.05)'
+}
+
+export const content: CSSProperties = {
+ padding: '20px 40px'
+}
+
+export const h1: CSSProperties = {
+ color: '#000',
+ marginBottom: '20px',
+ fontSize: '24px',
+ fontWeight: '600'
+}
+
+export const text: CSSProperties = {
+ marginBottom: '5px',
+ color: '#666'
+}
+
+export const workspaceDetails: CSSProperties = {
+ width: '100%',
+ backgroundColor: '#fafafa',
+ borderRadius: '5px',
+ margin: '20px 0px',
+ padding: '10px 20px'
+}
+
+export const workspaceInfo: CSSProperties = {
+ margin: '7px 0px'
+}
+
+export const ctaButton: CSSProperties = {
+ width: '100px',
+ color: '#ffffff',
+ fontSize: '14px',
+ fontWeight: '500',
+ textAlign: 'center',
+ marginTop: '10px',
+ cursor: 'pointer',
+ display: 'inline-block',
+ backgroundColor: '#000',
+ textDecoration: 'none',
+ padding: '10px 22px',
+ borderRadius: '5px'
+}
+
+export const footer: CSSProperties = {
+ borderTop: '1px solid #eaeaea',
+ padding: '20px'
+}
+
+export const footerText: CSSProperties = {
+ fontSize: '12px',
+ color: '#999',
+ textAlign: 'center' as const,
+ margin: '0'
+}
+
+export const link: CSSProperties = {
+ color: '#000',
+ textDecoration: 'underline'
+}
diff --git a/apps/api/src/mail/emails/workspace-invitation.tsx b/apps/api/src/mail/emails/workspace-invitation.tsx
new file mode 100644
index 00000000..37b4fc0f
--- /dev/null
+++ b/apps/api/src/mail/emails/workspace-invitation.tsx
@@ -0,0 +1,67 @@
+import * as React from 'react'
+import { Button, Section, Text } from '@react-email/components'
+import dayjs from 'dayjs'
+import {
+ ctaButton,
+ text,
+ workspaceDetails,
+ workspaceInfo
+} from './styles/common-styles'
+import BaseEmailTemplate from './components/base-email-template'
+
+interface WorkspaceInvitationEmailProps {
+ workspaceName: string
+ actionUrl: string
+ invitedBy: string
+ invitedOn: string
+ forRegisteredUser: boolean
+}
+
+export const WorkspaceInvitationEmail = ({
+ workspaceName,
+ actionUrl,
+ invitedBy,
+ invitedOn,
+ forRegisteredUser
+}: WorkspaceInvitationEmailProps) => {
+ const formattedInvitedOnDate = dayjs(invitedOn).format(
+ 'ddd, MMM D, YYYY h:mm A'
+ )
+
+ const previewText = forRegisteredUser
+ ? 'Welcome Back! Join Your Workspace'
+ : 'You are Invited to Join the Workspace'
+
+ return (
+
+ Dear User,
+
+ We're excited to inform you that you've been invited to join a
+ workspace on Keyshade. Here are the details of your invitation:
+
+
+
+ Workspace Name: {workspaceName}
+
+
+ Invited By: {invitedBy}
+
+
+ Invited On: {formattedInvitedOnDate}
+
+
+
+ Join the project by clicking the button below - we're excited to
+ have you!
+
+
+
+ )
+}
+
+export default WorkspaceInvitationEmail
\ No newline at end of file
diff --git a/apps/api/src/mail/emails/workspace-removal.tsx b/apps/api/src/mail/emails/workspace-removal.tsx
index da1ed0aa..29a28693 100644
--- a/apps/api/src/mail/emails/workspace-removal.tsx
+++ b/apps/api/src/mail/emails/workspace-removal.tsx
@@ -1,16 +1,8 @@
import * as React from 'react'
-import {
- Body,
- Container,
- Head,
- Heading,
- Html,
- Link,
- Preview,
- Section,
- Text
-} from '@react-email/components'
+import { Text, Section } from '@react-email/components'
import dayjs from 'dayjs'
+import { text, workspaceDetails, workspaceInfo } from './styles/common-styles'
+import BaseEmailTemplate from './components/base-email-template'
interface WorkspaceRemovalEmailProps {
workspaceName: string
@@ -26,126 +18,25 @@ export const RemovedFromWorkspaceEmail = ({
)
return (
-
-
-
-
- Removal from Workspace
- Dear User,
-
- We hope this email finds you well. We are writing to inform you
- that your access to the following workspace has been removed:
-
-
-
- Workspace Name: {workspaceName}
-
-
- Removed On: {formattedRemovedOnDate}
-
-
-
- If you believe this action was taken in error or have any
- questions regarding this change, please contact your project
- administrator or our support team.
-
-
- We appreciate your understanding and thank you for your
- contributions to the project.
-
-
- Cheers,
-
- Team Keyshade
-
-
-
-
- This is an automated message. Please do not reply to this email.
-
-
- Read our{' '}
-
- Privacy Policy
- {' '}
- and{' '}
-
- Terms and Conditions
- {' '}
- for more information on how we manage your data and services.
-
-
-
-
-
+
+ Dear User,
+
+ We hope this email finds you well. We are writing to inform you
+ that your access to the following workspace has been removed:
+
+
+
+ Workspace Name: {workspaceName}
+
+
+ Removed On: {formattedRemovedOnDate}
+
+
+
)
}
-export default RemovedFromWorkspaceEmail
-
-const main = {
- fontFamily: "'Segoe UI', 'Roboto', sans-serif",
- lineHeight: '1.6',
- color: '#04050a',
- backgroundColor: '#fafafa',
- margin: '0',
- padding: '20px'
-}
-
-const container = {
- maxWidth: '600px',
- margin: '0 auto',
- backgroundColor: '#fff',
- borderRadius: '5px',
- boxShadow: '0 2px 8px rgba(0, 0, 0, 0.05)'
-}
-
-const content = {
- padding: '20px 40px'
-}
-
-const h1 = {
- color: '#000',
- marginBottom: '20px',
- fontSize: '24px',
- fontWeight: '600'
-}
-
-const text = {
- marginBottom: '5px',
- color: '#666'
-}
-
-const workspaceDetails = {
- width: '100%',
- backgroundColor: '#fafafa',
- borderRadius: '5px',
- margin: '20px 0px',
- padding: '10px 20px'
-}
-
-const workspaceInfo = {
- margin: '7px 0px'
-}
-
-const footer = {
- borderTop: '1px solid #eaeaea',
- padding: '20px'
-}
-
-const footerText = {
- fontSize: '12px',
- color: '#999',
- textAlign: 'center' as const,
- margin: '0'
-}
-
-const link = {
- color: '#000',
- textDecoration: 'underline'
-}
+export default RemovedFromWorkspaceEmail
\ No newline at end of file
diff --git a/apps/api/src/mail/services/interface.service.ts b/apps/api/src/mail/services/interface.service.ts
index 539a8b40..a600be93 100644
--- a/apps/api/src/mail/services/interface.service.ts
+++ b/apps/api/src/mail/services/interface.service.ts
@@ -5,11 +5,12 @@ export interface IMailService {
sendEmailChangedOtp(email: string, otp: string): Promise
- workspaceInvitationMailForUsers(
+ invitedToWorkspace(
email: string,
- workspace: string,
- actionUrl: string,
+ workspaceName: string,
+ projectUrl: string,
invitedBy: string,
+ invitedOn: string,
forRegisteredUser: boolean
): Promise
diff --git a/apps/api/src/mail/services/mail.service.ts b/apps/api/src/mail/services/mail.service.ts
index dc5f4dfd..3a7dca54 100644
--- a/apps/api/src/mail/services/mail.service.ts
+++ b/apps/api/src/mail/services/mail.service.ts
@@ -7,6 +7,7 @@ import { IMailService } from './interface.service'
import { Transporter, createTransport } from 'nodemailer'
import RemovedFromWorkspaceEmail from '../emails/workspace-removal'
import { render } from '@react-email/render'
+import WorkspaceInvitationEmail from '../emails/workspace-invitation'
@Injectable()
export class MailService implements IMailService {
@@ -24,33 +25,27 @@ export class MailService implements IMailService {
}
})
}
- async workspaceInvitationMailForUsers(
+ async invitedToWorkspace(
email: string,
- workspace: string,
+ workspaceName: string,
actionUrl: string,
- invitee: string,
+ invitedBy: string,
+ invitedOn: string,
forRegisteredUser: boolean
): Promise {
- const subject = `You have been invited to a ${workspace}`
- const intro = forRegisteredUser
- ? `Hello again! You've been invited to join a new workspace.`
- : `Hello there! We're excited to welcome you to Keyshade.`
- const body = `
-
-
- Workspace Invitation
-
-
- Welcome to keyshade!
- ${intro}
- You have been invited to join the workspace ${workspace} by ${invitee}.
- Please click on the link below to accept the invitation.
- Accept Invitation
- Thank you for choosing us.
- Best Regards,
- keyshade Team
-
- `
+ const subject = forRegisteredUser
+ ? 'Welcome Back! Join Your Workspace'
+ : 'You are Invited to Join the Workspace'
+
+ const body = await render(
+ WorkspaceInvitationEmail({
+ workspaceName,
+ actionUrl,
+ invitedBy,
+ invitedOn,
+ forRegisteredUser
+ })
+ )
await this.sendEmail(email, subject, body)
}
diff --git a/apps/api/src/mail/services/mock.service.ts b/apps/api/src/mail/services/mock.service.ts
index 2a07f1e8..3513313e 100644
--- a/apps/api/src/mail/services/mock.service.ts
+++ b/apps/api/src/mail/services/mock.service.ts
@@ -5,17 +5,18 @@ import { IMailService } from './interface.service'
export class MockMailService implements IMailService {
private readonly log = new Logger(MockMailService.name)
- async workspaceInvitationMailForUsers(
+ async invitedToWorkspace(
email: string,
- workspace: string,
+ workspaceName: string,
actionUrl: string,
- invitee: string,
+ invitedBy: string,
+ invitedOn: string,
forRegisteredUser: boolean
): Promise {
this.log.log(
forRegisteredUser
- ? `Workspace Invitation Mail for Registered User: ${email}, ${workspace}, ${actionUrl}, ${invitee}`
- : `Workspace Invitation Mail for Non Registered User: ${email}, ${workspace}, ${actionUrl}, ${invitee}`
+ ? `User ${email} has been invited to the workspace ${workspaceName} by ${invitedBy} on ${invitedOn}. Invitation details can be accessed at ${actionUrl}.`
+ : `User ${email} has been invited to the workspace ${workspaceName} by ${invitedBy} on ${invitedOn}. Since the user is not registered, they can sign up and access the invitation details at ${actionUrl}.`
)
}
diff --git a/apps/api/src/workspace-membership/service/workspace-membership.service.ts b/apps/api/src/workspace-membership/service/workspace-membership.service.ts
index e9f07e3d..d10032c5 100644
--- a/apps/api/src/workspace-membership/service/workspace-membership.service.ts
+++ b/apps/api/src/workspace-membership/service/workspace-membership.service.ts
@@ -899,11 +899,12 @@ export class WorkspaceMembershipService {
if (memberUser) {
await this.prisma.$transaction([createMembership])
- this.mailService.workspaceInvitationMailForUsers(
+ this.mailService.invitedToWorkspace(
member.email,
workspace.name,
`${process.env.WORKSPACE_FRONTEND_URL}/workspace/${workspace.slug}/join`,
currentUser.name,
+ new Date().toISOString(),
true
)
@@ -925,7 +926,7 @@ export class WorkspaceMembershipService {
this.log.debug(`Created non-registered user ${memberUser}`)
- this.mailService.workspaceInvitationMailForUsers(
+ this.mailService.invitedToWorkspace(
member.email,
workspace.name,
`${process.env.WORKSPACE_FRONTEND_URL}/workspace/${
@@ -934,6 +935,7 @@ export class WorkspaceMembershipService {
id: userId
})}`,
currentUser.name,
+ new Date().toISOString(),
false
)