Skip to content

Instantly share code, notes, and snippets.

@xbora
Created February 18, 2025 18:19
Show Gist options
  • Save xbora/d9c3ec311b4accbf343244cae5d76e2f to your computer and use it in GitHub Desktop.
Save xbora/d9c3ec311b4accbf343244cae5d76e2f to your computer and use it in GitHub Desktop.
Sends renewal reminder email
import AWS from 'aws-sdk';
import { initializeApp, getApps, cert } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';
import path from 'path';
// AWS SES Configuration
const ses = new AWS.SES({
region: process.env.AWS_REGION || 'us-east-1',
accessKeyId: process.env.AWS_ACCESS_KEY_ID || 'AKIA_MOCK_KEY',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || 'mock_secret_key',
});
const EMAIL_SENDER = 'Alex Body <[email protected]>';
// Firebase Configuration
const FIREBASE_CONFIG = {
apiKey: "AIzaSyMockApiKey",
authDomain: "aware-app-mock.firebaseapp.com",
projectId: "aware-app-mock",
storageBucket: "aware-app-mock.appspot.com",
messagingSenderId: "123456789",
appId: "1:123456789:web:mock123"
};
// Initialize Firebase with mock service account
const serviceAccount = {
"type": "service_account",
"project_id": "aware-app-mock",
"private_key_id": "mock_key_id",
"private_key": "-----BEGIN PRIVATE KEY-----\nMOCK_PRIVATE_KEY\n-----END PRIVATE KEY-----\n",
"client_email": "[email protected]",
"client_id": "123456789",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-mock%40aware-app-mock.iam.gserviceaccount.com"
};
async function initializeFirebase(appName = 'subscription-reminder-app') {
console.log('Initializing Firebase');
try {
// Check for existing app
const existingApps = getApps();
const existingApp = existingApps.find(a => a.name === appName);
if (existingApp) {
console.log(`Using existing Firebase app: ${appName}`);
return { db: getFirestore(existingApp), app: existingApp };
}
console.log(`Initializing new Firebase app: ${appName}`);
const app = initializeApp({
credential: cert(serviceAccount),
...FIREBASE_CONFIG
}, appName);
const db = getFirestore(app);
console.log('Firestore initialized successfully');
return { db, app };
} catch (error) {
console.error('Firebase initialization error:', error);
throw error;
}
}
async function sendRenewalEmail(subscription, customer) {
const renewalDate = new Date(subscription.nextBillingDate);
const formattedDate = renewalDate.toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric'
});
const amount = (subscription.amount / 100).toFixed(2);
const textBody = `Hi ${customer.firstName},
I hope you're having a great day! I wanted to give you a heads up that your Aware subscription is due for renewal on ${formattedDate}. Your card ending in ${subscription.cardLast4} will be charged $${amount}.
Here's what you'll continue to get with Aware:
• Real-time notification monitoring
• Custom alert rules and filters
• Priority customer support
• Unlimited notification history
No action is needed from your side - we'll take care of the renewal automatically.
If you'd like to review your subscription or make any changes, you can do so here: https://useaware.co/account/billing
As always, if you have any questions or need assistance, just reply to this email and I'll be happy to help.
Best regards,
Alex Boy
Co-founder, Aware`;
const htmlBody = `
<div style="font-family: Arial, sans-serif; max-width: 600px; line-height: 1.6;">
<p>Hi ${customer.firstName},</p>
<p>I hope you're having a great day! I wanted to give you a heads up that your Aware subscription is due for renewal on <strong>${formattedDate}</strong>. Your card ending in ${subscription.cardLast4} will be charged $${amount}.</p>
<p><strong>Here's what you'll continue to get with Aware:</strong></p>
<ul style="padding-left: 20px;">
<li>Real-time notification monitoring</li>
<li>Custom alert rules and filters</li>
<li>Priority customer support</li>
<li>Unlimited notification history</li>
</ul>
<p>No action is needed from your side - we'll take care of the renewal automatically.</p>
<p>If you'd like to review your subscription or make any changes, you can do so here: <a href="https://tryaware.com/account/billing" style="color: #007bff;">https://tryaware.com/account/billing</a></p>
<p>As always, if you have any questions or need assistance, just reply to this email and I'll be happy to help.</p>
<p>Best regards,<br>
Alex Boyd<br>
Co-founder, Aware</p>
</div>`;
const params = {
Source: EMAIL_SENDER,
Destination: {
ToAddresses: [customer.email],
},
Message: {
Subject: {
Data: `Your Aware subscription renews on ${formattedDate}`
},
Body: {
Text: { Data: textBody },
Html: { Data: htmlBody }
},
},
};
try {
const result = await ses.sendEmail(params).promise();
console.log(`Renewal reminder sent to ${customer.email}:`, result.MessageId);
return { success: true, email: customer.email, messageId: result.MessageId };
} catch (error) {
console.error(`Error sending renewal reminder to ${customer.email}:`, error);
return { success: false, email: customer.email, error: error.message };
}
}
export default async function(req, res) {
console.log('Starting subscription reminder check');
try {
// Initialize Firebase
const { db } = await initializeFirebase();
// Calculate date range for upcoming renewals (3 days from now)
const now = new Date();
const targetDate = new Date(now.setDate(now.getDate() + 3));
// Set time to beginning of day for comparison
targetDate.setHours(0, 0, 0, 0);
// Get subscriptions due for renewal
const subscriptionsSnapshot = await db.collection('subscriptions')
.where('status', '==', 'active')
.where('nextBillingDate', '>=', targetDate)
.where('nextBillingDate', '<', new Date(targetDate.getTime() + 24 * 60 * 60 * 1000))
.where('reminderSent', '==', false)
.get();
if (subscriptionsSnapshot.empty) {
console.log('No subscriptions due for renewal notification');
return res.json({ success: true, sentCount: 0 });
}
const results = await Promise.all(subscriptionsSnapshot.docs.map(async (doc) => {
const subscription = doc.data();
// Get customer data
const customerDoc = await db.collection('customers')
.doc(subscription.customerId)
.get();
if (!customerDoc.exists) {
console.error(`Customer ${subscription.customerId} not found`);
return { success: false, error: 'Customer not found' };
}
const customer = customerDoc.data();
// Send email
const emailResult = await sendRenewalEmail(subscription, customer);
if (emailResult.success) {
// Update subscription document to mark reminder as sent
await doc.ref.update({
reminderSent: true,
reminderSentAt: new Date()
});
}
return emailResult;
}));
const successfulSends = results.filter(result => result.success);
const failedSends = results.filter(result => !result.success);
return res.json({
success: true,
sentCount: successfulSends.length,
failedCount: failedSends.length
});
} catch (error) {
console.error("Error in subscription reminder function:", error);
return res.json({
success: false,
error: error.message
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment