Skip to content

Commit 39a32eb

Browse files
author
Lasim
committed
feat(backend): implement welcome email functionality for new users
1 parent f446a1e commit 39a32eb

File tree

5 files changed

+160
-33
lines changed

5 files changed

+160
-33
lines changed

services/backend/src/email/emailService.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as nodemailer from 'nodemailer';
22
import type { Transporter } from 'nodemailer';
33
import type { FastifyBaseLogger } from 'fastify';
44
import { GlobalSettingsService } from '../services/globalSettingsService';
5+
import { GlobalSettings } from '../global-settings/helpers';
56
import { TemplateRenderer } from './templateRenderer';
67
import {
78
SendEmailOptionsSchema,
@@ -293,6 +294,15 @@ export class EmailService {
293294
}
294295
}
295296

297+
/**
298+
* Check if welcome emails should be sent
299+
*/
300+
static async shouldSendWelcomeEmail(): Promise<boolean> {
301+
const smtpEnabled = await GlobalSettings.getBoolean('smtp.enabled', false);
302+
const welcomeEmailEnabled = await GlobalSettings.getBoolean('global.send_welcome_email', false);
303+
return smtpEnabled && welcomeEmailEnabled;
304+
}
305+
296306
/**
297307
* Send a welcome email (type-safe helper)
298308
*/

services/backend/src/email/templates/welcome.pug

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,52 +6,63 @@ block content
66
h1 Welcome to DeployStack, #{userName}!
77

88
p
9-
| We're excited to have you join our community. Your account has been successfully created and you're ready to start deploying your applications with ease.
10-
11-
p
12-
strong Your account details:
13-
br
14-
| Email: #{userEmail}
15-
br
16-
| Registration Date: #{new Date().toLocaleDateString()}
9+
| Thanks for joining our mission to simplify MCP complexity for development teams. Your account is now active, and you're ready to eliminate credential management hassles and get productive with MCP tools in minutes, not hours.
1710

1811
.text-center
19-
a.button(href=loginUrl) Get Started - Login to Your Account
12+
a.button(href=loginUrl) Install DeployStack Gateway
13+
14+
h2 Ready to get started?
2015

21-
h2 What's Next?
16+
p Here's how to get your MCP tools working instantly:
2217

23-
p Here are some things you can do to get started:
18+
.steps
19+
p
20+
strong 1. Install the gateway:
21+
br
22+
code npm install -g @deploystack/gateway
23+
24+
p
25+
strong 2. Login to your account:
26+
br
27+
code deploystack login
28+
29+
p
30+
strong 3. Start using your team's MCP tools:
31+
br
32+
code deploystack start
33+
34+
p
35+
strong 4. Configure VS Code:
36+
br
37+
| Replace your MCP config with our gateway endpoint:
38+
br
39+
code "url": "http://localhost:9095/sse"
40+
41+
h2 What's next?
2442

2543
ul
44+
li
45+
strong Add your first MCP server&nbsp;
46+
| - Browse our catalog and add tools like GitHub, BrightData, or Weather
2647
li
27-
strong Set up your first project
28-
| - Create your first deployment stack and configure your environment
29-
li
30-
strong Explore the dashboard
31-
| - Familiarize yourself with the interface and available features
32-
li
33-
strong Configure integrations
34-
| - Connect your favorite tools and services to streamline your workflow
35-
li
36-
strong Join our community
37-
| - Connect with other developers and share your experiences
48+
strong Invite team members&nbsp;
49+
| - Share secure access to MCP tools without credential sharing
3850

39-
h2 Need Help?
51+
h2 Need help?
4052

41-
p
42-
| If you have any questions or need assistance getting started, our support team is here to help. You can:
53+
p We're here to help you succeed:
4354

4455
ul
45-
li Check out our documentation and tutorials
46-
li Browse our FAQ section
47-
if supportEmail
48-
li
49-
| Contact our support team at
50-
a(href=`mailto:${supportEmail}`)= supportEmail
51-
li Join our community forums for tips and discussions
56+
li 📚 Check our
57+
a(href="https://docs.deploystack.io/quick-start") quick start guide
58+
li 💬 Join our
59+
a(href="https://discord.gg/42Ce3S7b3b") Discord community
60+
li 🔍 Browse the
61+
a(href=loginUrl) MCP catalog
62+
| for available tools
5263

5364
p
54-
| Thank you for choosing DeployStack. We look forward to helping you streamline your deployment process!
65+
| We're excited to see what you'll build with zero-configuration MCP access!
5566

5667
p.text-muted
5768
| Best regards,

services/backend/src/global-settings/global.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ export const globalSettings: GlobalSettingsModule = {
6464
description: 'Maximum number of teams a user can create. This includes both default and custom teams.',
6565
encrypted: false,
6666
required: false
67+
},
68+
{
69+
key: 'global.send_welcome_email',
70+
defaultValue: false,
71+
type: 'boolean',
72+
description: 'Send welcome email to users when they verify their email or login via OAuth flows (GitHub, etc.)',
73+
encrypted: false,
74+
required: false
6775
}
6876
]
6977
};

services/backend/src/routes/auth/github.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { eq } from 'drizzle-orm';
99
import { generateId } from 'lucia';
1010
import { generateState } from 'arctic';
1111
import { GlobalSettingsInitService } from '../../global-settings';
12+
import { EmailService } from '../../email';
13+
import { GlobalSettings } from '../../global-settings/helpers';
1214

1315
// Response schemas for GitHub OAuth API
1416
const errorResponseSchema = z.object({
@@ -320,6 +322,41 @@ export default async function githubAuthRoutes(fastify: FastifyInstance) {
320322

321323
await (db as any).insert(authUserTable).values(newUserData);
322324

325+
// Send welcome email if enabled (for new OAuth users)
326+
try {
327+
const shouldSendWelcome = await EmailService.shouldSendWelcomeEmail();
328+
if (shouldSendWelcome) {
329+
const userName = newUserData.first_name
330+
? `${newUserData.first_name}${newUserData.last_name ? ` ${newUserData.last_name}` : ''}`
331+
: newUserData.username || 'User';
332+
333+
const loginUrl = await GlobalSettings.get('global.page_url', 'http://localhost:5173') + '/login';
334+
const supportEmail = await GlobalSettings.get('smtp.from_email') || undefined;
335+
336+
// Send welcome email asynchronously (don't block OAuth flow)
337+
EmailService.sendWelcomeEmail({
338+
to: newUserData.email,
339+
userName,
340+
userEmail: newUserData.email,
341+
loginUrl,
342+
supportEmail
343+
}, fastify.log).catch(error => {
344+
fastify.log.warn({
345+
error,
346+
userId: newUserId,
347+
operation: 'send_welcome_email_after_oauth_signup'
348+
}, 'Failed to send welcome email after GitHub OAuth signup');
349+
});
350+
}
351+
} catch (error) {
352+
// Don't fail OAuth if welcome email fails
353+
fastify.log.warn({
354+
error,
355+
userId: newUserId,
356+
operation: 'send_welcome_email_after_oauth_signup'
357+
}, 'Error occurred while trying to send welcome email after GitHub OAuth signup');
358+
}
359+
323360
// Create default team for the user
324361
try {
325362
const { TeamService } = await import('../../services/teamService');

services/backend/src/routes/auth/verifyEmail.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { FastifyInstance } from 'fastify';
22
import { EmailVerificationService } from '../../services/emailVerificationService';
3+
import { EmailService } from '../../email';
4+
import { GlobalSettings } from '../../global-settings/helpers';
5+
import { getDb, getSchema } from '../../db';
6+
import { eq } from 'drizzle-orm';
37

48
// Reusable Schema Constants
59
const VERIFY_EMAIL_QUERYSTRING_SCHEMA = {
@@ -109,6 +113,63 @@ export default async function verifyEmailRoute(server: FastifyInstance) {
109113
return reply.status(400).type('application/json').send(jsonString);
110114
}
111115

116+
// Send welcome email if enabled
117+
try {
118+
const shouldSendWelcome = await EmailService.shouldSendWelcomeEmail();
119+
if (shouldSendWelcome && result.userId) {
120+
// Get user details to send welcome email
121+
const db = getDb();
122+
const schema = getSchema();
123+
const authUserTable = schema.authUser;
124+
125+
if (authUserTable) {
126+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
127+
const users = await (db as any)
128+
.select({
129+
email: authUserTable.email,
130+
first_name: authUserTable.first_name,
131+
last_name: authUserTable.last_name,
132+
username: authUserTable.username
133+
})
134+
.from(authUserTable)
135+
.where(eq(authUserTable.id, result.userId))
136+
.limit(1);
137+
138+
if (users.length > 0) {
139+
const user = users[0];
140+
const userName = user.first_name
141+
? `${user.first_name}${user.last_name ? ` ${user.last_name}` : ''}`
142+
: user.username || 'User';
143+
144+
const loginUrl = await GlobalSettings.get('global.page_url', 'http://localhost:5173') + '/login';
145+
const supportEmail = await GlobalSettings.get('smtp.from_email') || undefined;
146+
147+
// Send welcome email asynchronously (don't block verification response)
148+
EmailService.sendWelcomeEmail({
149+
to: user.email,
150+
userName,
151+
userEmail: user.email,
152+
loginUrl,
153+
supportEmail
154+
}, request.log).catch(error => {
155+
request.log.warn({
156+
error,
157+
userId: result.userId,
158+
operation: 'send_welcome_email_after_verification'
159+
}, 'Failed to send welcome email after email verification');
160+
});
161+
}
162+
}
163+
}
164+
} catch (error) {
165+
// Don't fail verification if welcome email fails
166+
request.log.warn({
167+
error,
168+
userId: result.userId,
169+
operation: 'send_welcome_email_after_verification'
170+
}, 'Error occurred while trying to send welcome email after verification');
171+
}
172+
112173
// Clean up expired tokens (housekeeping)
113174
EmailVerificationService.cleanupExpiredTokens().catch(error => {
114175
server.log.warn('Failed to cleanup expired tokens:', error);

0 commit comments

Comments
 (0)