Skip to main content
The Telegram Subscription Manager uses cron jobs to automatically remove users from the VIP channel when their subscriptions expire. This ensures that only active paying members have access.

Overview

The cron system performs the following tasks:
  1. Checks for expired subscriptions every hour
  2. Removes users from the Telegram channel by banning them
  3. Deletes MetaCopier accounts for premium users
  4. Sends expiry notifications to removed users
  5. Updates the database to mark subscriptions as removed
The cron job runs automatically when deployed on Vercel. No manual configuration needed after setup.

Cron Endpoint

The cron job is implemented as an API route: Endpoint: /api/cron/remove-expired File: src/app/api/cron/remove-expired/route.ts Methods: GET and POST (POST is for manual testing) Schedule: Every hour (0 * * * * in cron syntax)

How It Works

Here’s the step-by-step flow when the cron job runs:

1. Find Expired Subscriptions

src/app/api/cron/remove-expired/route.ts
const now = new Date()
const expiredSubscriptions = await prisma.subscription.findMany({
  where: {
    expiresAt: { lt: now },  // Expiry date is in the past
    isRemoved: false          // Not already removed
  },
  include: { mt5Setup: true }
})

2. Process Each Expired Subscription

For each expired user:
1

Remove MetaCopier Account

If the user has premium plan with copier access:
if (subscription.mt5Setup && subscription.mt5Setup.metacopierAccountId) {
  await removeUserMt5Account(
    subscription.mt5Setup.metacopierAccountId,
    subscription.mt5Setup.metacopierCopierId
  )
}
If MetaCopier removal fails, an alert is sent to the admin for manual cleanup.
2

Ban User from Channel

Remove the user from the Telegram VIP channel:
const banned = await banChatMember(subscription.telegramUserId)
3

Update Database

Mark the subscription as removed:
await prisma.subscription.update({
  where: { id: subscription.id },
  data: {
    isRemoved: true,
    removedAt: now
  }
})
4

Notify User

Send an expiry notification with renewal options:
await sendMessage(
  subscription.telegramUserId,
  `⏰ Your subscription has expired.
  
  Your access to Pear VIP signals channel has been removed.
  
  Want to renew? Just tap /pay to get started.`
)

3. Return Results

return NextResponse.json({
  success: true,
  processed: expiredSubscriptions.length,
  removed: removedCount,
  failed: failedCount,
  timestamp: now.toISOString()
})

Vercel Cron Configuration

To enable automatic hourly execution on Vercel, create a vercel.json file in your project root:
vercel.json
{
  "crons": [
    {
      "path": "/api/cron/remove-expired",
      "schedule": "0 * * * *"
    }
  ]
}
path
string
The API route to call (always /api/cron/remove-expired)
schedule
string
Cron expression for the schedule. Format: minute hour day month weekdayExamples:
  • 0 * * * * - Every hour at minute 0
  • */30 * * * * - Every 30 minutes
  • 0 */6 * * * - Every 6 hours
  • 0 0 * * * - Daily at midnight
  • 0 2 * * * - Daily at 2 AM

Customizing the Schedule

Run Every 30 Minutes

vercel.json
{
  "crons": [
    {
      "path": "/api/cron/remove-expired",
      "schedule": "*/30 * * * *"
    }
  ]
}

Run Every 6 Hours

vercel.json
{
  "crons": [
    {
      "path": "/api/cron/remove-expired",
      "schedule": "0 */6 * * *"
    }
  ]
}

Run Daily at 2 AM

vercel.json
{
  "crons": [
    {
      "path": "/api/cron/remove-expired",
      "schedule": "0 2 * * *"
    }
  ]
}
More frequent runs consume more serverless function invocations. Hourly is recommended for balancing timely removals with resource usage.

Manual Testing

You can manually trigger the cron job for testing:

Using cURL

curl -X POST https://your-domain.vercel.app/api/cron/remove-expired

Using Browser

Simply visit:
https://your-domain.vercel.app/api/cron/remove-expired

Expected Response

{
  "success": true,
  "processed": 5,
  "removed": 4,
  "failed": 1,
  "timestamp": "2026-03-03T10:00:00.000Z"
}
processed
number
Total number of expired subscriptions found
removed
number
Number of users successfully removed from channel
failed
number
Number of removals that failed (will retry next run)
timestamp
string
ISO 8601 timestamp when the cron job ran

Error Handling

The cron job implements robust error handling:

MetaCopier Removal Failures

If MetaCopier account deletion fails, the admin receives a notification:
⚠️ MetaCopier Removal Failed - Manual Action Needed!

━━━━━━━━━━━━━━━━━━━

User: 123456789 (username)
MetaCopier Account: mc_abc123
Error: API timeout

━━━━━━━━━━━━━━━━━━━

Action Required:
• User has been removed from channel
• But MetaCopier account could NOT be removed automatically
• Please remove manually from MetaCopier dashboard

Login: 12345678
Server: MetaQuotes-Demo
The channel removal continues even if MetaCopier cleanup fails, ensuring users can’t access VIP content.

Telegram Ban Failures

If banning a user fails:
  • The subscription is not marked as removed
  • The failure is logged and counted
  • The job will retry on the next run

Database Errors

If the database query fails, the endpoint returns:
{
  "error": "Cron job failed",
  "message": "Database connection timeout"
}

Monitoring

Check Vercel Cron Logs

  1. Go to Vercel Dashboard
  2. Select your project
  3. Navigate to DeploymentsFunctions
  4. Click on /api/cron/remove-expired
  5. View execution logs and metrics

Database Monitoring

Query removed subscriptions:
npx prisma studio
Filter by:
  • isRemoved: true
  • removedAt: [recent date range]

Admin Notifications

The cron job sends Telegram messages to the admin (configured in ADMIN_ID) when:
  • MetaCopier removal fails
  • Critical errors occur during processing

Best Practices

Production Checklist:
  • ✅ Set CRON_SECRET in environment variables
  • ✅ Test cron endpoint manually before going live
  • ✅ Verify bot has “Restrict members” permission in channel
  • ✅ Monitor logs regularly for failures
  • ✅ Set up admin notifications
  • ✅ Have a manual removal process as backup
Performance Tips:
  • The cron job processes all expired subscriptions in a single run
  • Failed removals are automatically retried on the next scheduled run
  • Running every hour balances timeliness with serverless function usage
  • Consider more frequent runs (every 30 minutes) for high-volume operations

Troubleshooting

Cron Job Not Running

  1. Check vercel.json exists in project root
  2. Verify deployment includes the cron configuration
  3. Review Vercel logs for errors
  4. Ensure endpoint returns 200 status code

Users Not Being Removed

  1. Check bot permissions in the Telegram channel
  2. Verify TELEGRAM_BOT_TOKEN is correct
  3. Review database - are subscriptions actually expired?
  4. Check isRemoved flag - might already be processed

Too Many Failed Removals

  1. Check Telegram API rate limits (20 requests/second)
  2. Verify bot hasn’t been removed from the channel
  3. Review error logs for specific failure reasons
  4. Consider adding retry delays between removals

Duplicate Removals

The system prevents duplicates by:
  • Checking isRemoved: false before processing
  • Setting isRemoved: true immediately after successful removal
  • Using database transactions for atomicity

Advanced Configuration

For more control, you can modify the cron job behavior in src/app/api/cron/remove-expired/route.ts:

Add Grace Period

const gracePeriodHours = 24
const cutoffDate = new Date(now.getTime() - (gracePeriodHours * 60 * 60 * 1000))

const expiredSubscriptions = await prisma.subscription.findMany({
  where: {
    expiresAt: { lt: cutoffDate },  // 24 hours past expiry
    isRemoved: false
  }
})

Batch Processing

const BATCH_SIZE = 10

for (let i = 0; i < expiredSubscriptions.length; i += BATCH_SIZE) {
  const batch = expiredSubscriptions.slice(i, i + BATCH_SIZE)
  await Promise.all(batch.map(processSubscription))
  await new Promise(resolve => setTimeout(resolve, 1000)) // Rate limit pause
}

Custom Notifications

Modify the expiry message sent to users in the sendMessage call within the route handler.

Build docs developers (and LLMs) love