Created
February 18, 2025 18:19
-
-
Save xbora/d9c3ec311b4accbf343244cae5d76e2f to your computer and use it in GitHub Desktop.
Sends renewal reminder email
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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