Browse Source

chore(api): send reminder emails to new inactive and free users

pull/58/head
isra el 12 months ago
parent
commit
b72e586d06
  1. 4
      api/src/billing/billing.module.ts
  2. 84
      api/src/mail/templates/inactive-new-user.hbs
  3. 94
      api/src/mail/templates/upgrade-to-pro.hbs
  4. 11
      api/src/users/users.module.ts
  5. 86
      api/src/users/users.service.ts

4
api/src/billing/billing.module.ts

@ -18,8 +18,8 @@ import { PolarWebhookPayload, PolarWebhookPayloadSchema } from './schemas/polar-
{ name: Subscription.name, schema: SubscriptionSchema },
{ name: PolarWebhookPayload.name, schema: PolarWebhookPayloadSchema },
]),
AuthModule,
UsersModule,
forwardRef(() => AuthModule),
forwardRef(() => UsersModule),
forwardRef(() => GatewayModule),
],
controllers: [BillingController],

84
api/src/mail/templates/inactive-new-user.hbs

@ -0,0 +1,84 @@
<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:
#4CAF50; 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:
#4CAF50; color: white; border-radius: 50%; text-align: center;
line-height: 25px; margin-right: 10px; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
{{! <img
src='https://textbee.dev/logo.png'
alt='textbee.dev Logo'
class='logo'
/> }}
</div>
<div class='content'>
<h2>Hello {{name}},</h2>
<p>Thank you for signing up for SMS Gateway! We noticed you haven't
registered any devices yet.</p>
<p>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>To get started, you'll need to register your first device:</p>
<div class='steps'>
<div class='step'>
<span class='step-number'>1</span>
Install the SMS Gateway app on your Android device
</div>
<div class='step'>
<span class='step-number'>2</span>
Log in with your account credentials
</div>
<div class='step'>
<span class='step-number'>3</span>
Grant the necessary permissions
</div>
<div class='step'>
<span class='step-number'>4</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>If you have any questions or need assistance, please don't hesitate
to contact our support team 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>

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

@ -0,0 +1,94 @@
<html>
<head>
<meta charset='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<title>Upgrade to Pro - 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; }
.feature { margin-bottom: 15px; display: flex; align-items: center; }
.feature-icon { display: inline-block; width: 20px; height: 20px;
background-color: #4A90E2; color: white; border-radius: 50%; text-align:
center; line-height: 20px; margin-right: 10px; font-weight: bold; }
.comparison { width: 100%; border-collapse: collapse; margin: 20px 0; }
.comparison th, .comparison td { padding: 10px; text-align: center;
border-bottom: 1px solid #ddd; } .comparison th { background-color:
#f2f2f2; } .highlight { background-color: #e6f2ff; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
{{! <img src="https://textbee.dev/logo.png" alt="textbee.dev Logo" class="logo"> }}
</div>
<div class='content'>
<h2>Hello {{name}},</h2>
<p>Thank you for using textbee.dev! We hope you're enjoying our service
so far.</p>
<p>We wanted to let you know that you're currently on our
<strong>Free Plan</strong>, which provides basic functionality. To
unlock the full potential of textbee.dev, consider upgrading to our
<strong>Pro Plan</strong>.</p>
<h3>Plan Comparison:</h3>
<table class='comparison'>
<tr>
<th>Feature</th>
<th>Free Plan</th>
<th class='highlight'>Pro Plan</th>
</tr>
<tr>
<td>Daily Message Limit</td>
<td>50</td>
<td class='highlight'>Unlimited</td>
</tr>
<tr>
<td>Monthly Recipient Limit</td>
<td>500</td>
<td class='highlight'>5,000</td>
</tr>
<tr>
<td>Device Limit</td>
<td>1</td>
<td class='highlight'>Unlimited</td>
</tr>
<tr>
<td>Bulk SMS Recipients</td>
<td>50</td>
<td class='highlight'>Unlimited</td>
</tr>
<tr>
<td>Support</td>
<td>Basic</td>
<td class='highlight'>Priority</td>
</tr>
</table>
<div style='text-align: center;'>
<a href='{{upgradeUrl}}' class='button'>Upgrade to Pro Now</a>
</div>
<p>If you have any questions about our plans or need assistance, please
feel free to contact us 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.</p>
</div>
</div>
</body>
</html>

11
api/src/users/users.module.ts

@ -1,8 +1,11 @@
import { Module } from '@nestjs/common'
import { forwardRef, Module } from '@nestjs/common'
import { MongooseModule } from '@nestjs/mongoose'
import { User, UserSchema } from './schemas/user.schema'
import { UsersController } from './users.controller'
import { UsersService } from './users.service'
import { BillingModule } from 'src/billing/billing.module'
import { Device, DeviceSchema } from 'src/gateway/schemas/device.schema'
import { MailModule } from 'src/mail/mail.module'
@Module({
imports: [
@ -11,7 +14,13 @@ import { UsersService } from './users.service'
name: User.name,
schema: UserSchema,
},
{
name: Device.name,
schema: DeviceSchema,
},
]),
forwardRef(() => BillingModule),
MailModule,
],
controllers: [UsersController],
providers: [UsersService],

86
api/src/users/users.service.ts

@ -2,10 +2,19 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose'
import { User, UserDocument } from './schemas/user.schema'
import { Model } from 'mongoose'
import { Cron, CronExpression } from '@nestjs/schedule'
import { MailService } from '../mail/mail.service'
import { BillingService } from '../billing/billing.service'
import { Device, DeviceDocument } from '../gateway/schemas/device.schema'
@Injectable()
export class UsersService {
constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}
constructor(
@InjectModel(User.name) private userModel: Model<UserDocument>,
@InjectModel(Device.name) private deviceModel: Model<DeviceDocument>,
private mailService: MailService,
private billingService: BillingService,
) {}
async findOne(params) {
return await this.userModel.findOne(params)
@ -62,4 +71,79 @@ export class UsersService {
return await userToUpdate.save()
}
@Cron('0 19 * * *') // Every day at 7 PM
async sendEmailToInactiveNewUsers() {
try {
// Get users who signed up in the last 24 hours
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000)
const newUsers = await this.userModel.find({
createdAt: { $gte: 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}`)
}
}
} catch (error) {
console.error('Error sending emails to inactive new users:', error)
}
}
@Cron('0 20 * * *') // Every day at 8 PM
async sendEmailToFreeUsers() {
try {
// Get users who signed up in the last 3 days
const threeDaysAgo = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000)
const recentUsers = await this.userModel.find({
createdAt: { $gte: threeDaysAgo },
})
for (const user of recentUsers) {
// 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}`)
}
}
} catch (error) {
console.error('Error sending emails to free plan users:', error)
}
}
}
Loading…
Cancel
Save