Browse Source

chore(api): update reminder emails

pull/58/head
isra el 12 months ago
parent
commit
98d596e61b
  1. 85
      api/src/mail/templates/inactive-new-user-day-3.hbs
  2. 38
      api/src/mail/templates/upgrade-to-pro.hbs
  3. 115
      api/src/users/users.service.ts

85
api/src/mail/templates/inactive-new-user-day-3.hbs

@ -0,0 +1,85 @@
<html>
<head>
<meta charset='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<title>Get Started with textbee.dev</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333;
margin: 0; padding: 0; } .container { max-width: 600px; margin: 0 auto;
padding: 20px; } .header { text-align: center; padding: 20px 0; } .logo {
max-width: 150px; } .content { background-color: #f9f9f9; padding: 30px;
border-radius: 5px; } .button { display: inline-block; background-color:
#4A90E2; color: white; text-decoration: none; padding: 12px 24px;
border-radius: 4px; font-weight: bold; margin: 20px 0; } .footer {
text-align: center; font-size: 12px; color: #777; margin-top: 30px; }
.steps { margin: 20px 0; } .step { margin-bottom: 15px; } .step-number {
display: inline-block; width: 25px; height: 25px; background-color:
#4A90E2; color: white; border-radius: 50%; text-align: center;
line-height: 25px; margin-right: 10px; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
</div>
<div class='content'>
<h2>Hello {{name}},</h2>
<p>We noticed it's been 3 days since you signed up for textbee.dev, but
you haven't registered any devices yet.</p>
<p>We'd love to help you get the most out of our platform! With
textbee.dev, you can:</p>
<ul>
<li>Send and receive SMS messages from your dashboard or API</li>
<li>Receive SMS notifications with webhooks</li>
<li>Integrate SMS functionality into your applications</li>
<li>And much more!</li>
</ul>
<p>Getting started is quick and easy:</p>
<div class='steps'>
<div class='step'>
<span class='step-number'>1</span>
Install the textbee.dev android app on your device
</div>
<div class='step'>
<span class='step-number'>2</span>
Grant the necessary permissions
</div>
<div class='step'>
<span class='step-number'>3</span>
Login to your
<a href='{{registerDeviceUrl}}'>dashboard</a>
and generate a QR code
</div>
<div class='step'>
<span class='step-number'>4</span>
Scan the QR code with the app to register your device
</div>
<div class='step'>
<span class='step-number'>5</span>
Start sending and receiving SMS!
</div>
</div>
<div style='text-align: center;'>
<a href='{{registerDeviceUrl}}' class='button'>Register Your Device
Now</a>
</div>
<p>Is there anything we can help you with? If you're experiencing any
issues or have questions, our support team is ready to assist at
<a href='mailto:support@textbee.dev'>support@textbee.dev</a>.</p>
<p>Best regards,<br />The textbee.dev Team</p>
</div>
<div class='footer'>
<p>© 2025 textbee.dev. All rights reserved.</p>
</div>
</div>
</body>
</html>

38
api/src/mail/templates/upgrade-to-pro.hbs

@ -25,7 +25,6 @@
<body> <body>
<div class='container'> <div class='container'>
<div class='header'> <div class='header'>
{{! <img src="https://textbee.dev/logo.png" alt="textbee.dev Logo" class="logo"> }}
</div> </div>
<div class='content'> <div class='content'>
@ -39,6 +38,12 @@
unlock the full potential of textbee.dev, consider upgrading to our unlock the full potential of textbee.dev, consider upgrading to our
<strong>Pro Plan</strong>.</p> <strong>Pro Plan</strong>.</p>
<p><strong>Special Offer Just For You:</strong>
We're extending a
<span style='color: #e63946; font-weight: bold;'>30% discount</span>
on all Pro plans exclusively for our most active users like you! This
offer is only available for a limited time.</p>
<h3>Plan Comparison:</h3> <h3>Plan Comparison:</h3>
<table class='comparison'> <table class='comparison'>
@ -75,8 +80,39 @@
</table> </table>
<div
style='background-color: #e6f2ff; padding: 15px; border-radius: 5px; margin-bottom: 20px; text-align: center;'
>
<h3 style='margin-top: 0; margin-bottom: 10px; color: #4A90E2;'>Pro
Plan Pricing</h3>
<div style='display: flex; justify-content: center; gap: 15px;'>
<div
style='padding: 10px; background-color: white; border-radius: 5px; flex: 1; max-width: 130px;'
>
<h4 style='margin: 0 0 5px 0;'>Monthly</h4>
<p style='font-size: 18px; font-weight: bold; margin: 5px 0;'><s
>$9.99</s>
<span style='color: #e63946;'>$6.99</span></p>
<p style='font-size: 11px; color: #666; margin: 0;'>30% off -
limited time</p>
</div>
<div
style='padding: 10px; background-color: white; border-radius: 5px; flex: 1; max-width: 130px;'
>
<h4 style='margin: 0 0 5px 0;'>Annual</h4>
<p style='font-size: 18px; font-weight: bold; margin: 5px 0;'><s
>$99.99</s>
<span style='color: #e63946;'>$69.99</span></p>
<p style='font-size: 11px; color: #666; margin: 0;'>30% off -
limited time</p>
</div>
</div>
</div>
<div style='text-align: center;'> <div style='text-align: center;'>
<a href='{{upgradeUrl}}' class='button'>Upgrade to Pro Now</a> <a href='{{upgradeUrl}}' class='button'>Upgrade to Pro Now</a>
<p><small>Hurry! This 30% discount offer is only available for a
limited time.</small></p>
</div> </div>
<p>If you have any questions about our plans or need assistance, please <p>If you have any questions about our plans or need assistance, please

115
api/src/users/users.service.ts

@ -72,7 +72,6 @@ export class UsersService {
return await userToUpdate.save() return await userToUpdate.save()
} }
@Cron('0 12 * * *') // Every day at 12 PM @Cron('0 12 * * *') // Every day at 12 PM
async sendEmailToInactiveNewUsers() { async sendEmailToInactiveNewUsers() {
try { try {
@ -83,75 +82,95 @@ export class UsersService {
const newUsers = await this.userModel.find({ const newUsers = await this.userModel.find({
createdAt: { createdAt: {
$gte: twoDaysAgo, $gte: twoDaysAgo,
$lt: oneDayAgo
$lt: oneDayAgo,
}, },
}) })
for (const user of newUsers) {
// Check if user has any devices
const devices = await this.deviceModel.find({ user: user._id })
if (devices.length === 0) {
// User hasn't registered any device, send email
await this.mailService.sendEmailFromTemplate({
to: user.email,
subject: 'Get Started with textbee.dev - Register Your First Device',
template: 'inactive-new-user',
context: {
name: user.name,
registerDeviceUrl: `${process.env.FRONTEND_URL}/dashboard`,
},
})
console.log(`Sent inactive new user email to ${user.email}`)
const emailPromises = newUsers.map(async (user) => {
try {
// Check if user has any devices registered
const devices = await this.deviceModel.find({ user: user._id })
if (devices.length === 0) {
// User hasn't registered any device, send email
await this.mailService.sendEmailFromTemplate({
to: user.email,
subject:
'Get Started with textbee.dev - Register Your First Device',
template: 'inactive-new-user',
context: {
name: user.name,
registerDeviceUrl: `${process.env.FRONTEND_URL}/dashboard`,
},
})
console.log(`Sent inactive new user email to ${user.email}`)
}
} catch (error) {
console.error(`Error processing email for user ${user.email}:`, error)
} }
}
})
await Promise.allSettled(emailPromises)
} catch (error) { } catch (error) {
console.error('Error sending emails to inactive new users:', error) console.error('Error sending emails to inactive new users:', error)
} }
} }
@Cron('0 13 * * *') // Every day at 1 PM @Cron('0 13 * * *') // Every day at 1 PM
async sendEmailToFreeUsers() { async sendEmailToFreeUsers() {
try { try {
// Get users who signed up exactly 3 days ago (within a 24-hour window)
// Get users who signed up between 3-4 days ago
const threeDaysAgo = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000) const threeDaysAgo = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000)
const fourDaysAgo = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000) const fourDaysAgo = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000)
const usersToEmail = await this.userModel.find({ const usersToEmail = await this.userModel.find({
createdAt: { createdAt: {
$gte: fourDaysAgo, $gte: fourDaysAgo,
$lt: threeDaysAgo
$lt: threeDaysAgo,
}, },
}) })
for (const user of usersToEmail) {
// Check if user is on free plan
const subscription = await this.billingService.getActiveSubscription(user._id.toString())
if (subscription && subscription.plan && subscription.plan.name === 'free') {
// User is still on free plan, send upgrade email
await this.mailService.sendEmailFromTemplate({
to: user.email,
subject: 'Upgrade to Pro for More Features - textbee.dev',
template: 'upgrade-to-pro',
context: {
name: user.name,
upgradeUrl: `${process.env.FRONTEND_URL}/checkout/pro`,
features: [
'Increased SMS sending limits',
'Priority support',
'Unlimited devices',
'Unlimited Bulk SMS Recipients',
'and more...'
]
},
})
console.log(`Sent upgrade to pro email to ${user.email}`)
const emailPromises = usersToEmail.map(async (user) => {
try {
const subscription = await this.billingService.getActiveSubscription(
user._id.toString(),
)
if (subscription?.plan?.name === 'free') {
const devices = await this.deviceModel.find({ user: user._id })
if (devices.length === 0) {
await this.mailService.sendEmailFromTemplate({
to: user.email,
subject: `${user.name?.split(' ')[0]}, Your textbee.dev account is waiting for you!`,
template: 'inactive-new-user-day-3',
context: {
name: user.name,
registerDeviceUrl: `${process.env.FRONTEND_URL}/dashboard`,
},
})
console.log(`Sent inactive new user email to ${user.email}`)
} else {
await this.mailService.sendEmailFromTemplate({
to: user.email,
subject: `${user.name?.split(' ')[0]}, Upgrade to Pro with a 30% Discount - textbee.dev`,
template: 'upgrade-to-pro',
context: {
name: user.name,
upgradeUrl: `${process.env.FRONTEND_URL}/checkout/pro`,
},
})
console.log(`Sent upgrade to pro email to ${user.email}`)
}
}
} catch (error) {
console.error(`Error processing email for user ${user.email}:`, error)
} }
}
})
await Promise.allSettled(emailPromises)
} catch (error) { } catch (error) {
console.error('Error sending emails to free plan users:', error) console.error('Error sending emails to free plan users:', error)
} }

Loading…
Cancel
Save