From 6074d45c540bbf3cad7956550a1e3308fe0df05b Mon Sep 17 00:00:00 2001
From: Ritam-Vaskar
Date: Mon, 12 Jan 2026 22:16:54 +0530
Subject: [PATCH 01/15] Update CORS configuration to include new allowed origin
and restrict access in production
---
index.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/index.js b/index.js
index 8aa91cd..e6720ff 100644
--- a/index.js
+++ b/index.js
@@ -121,13 +121,14 @@ app.use(cors({
"http://localhost:5173",
"http://localhost:3000",
"https://fedkiit.com",
- "https://www.fedkiit.com"
+ "https://www.fedkiit.com",
+ "https://awsapi2.fedkiit.com"
];
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
- callback(null, true); // Allow all in dev, restrict in production
+ callback(new Error('Not allowed by CORS'));
}
},
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
From 77747c448f00a0a8cccb72f0052ee564c747a6c5 Mon Sep 17 00:00:00 2001
From: Ritam-Vaskar
Date: Mon, 12 Jan 2026 22:30:00 +0530
Subject: [PATCH 02/15] Update CORS configuration to allow all origins
---
index.js | 18 ++----------------
1 file changed, 2 insertions(+), 16 deletions(-)
diff --git a/index.js b/index.js
index e6720ff..452100c 100644
--- a/index.js
+++ b/index.js
@@ -114,22 +114,8 @@ app.use(cookieParser());
app.use(cors({
origin: function (origin, callback) {
- // Allow requests with no origin (like mobile apps or curl)
- if (!origin) return callback(null, true);
-
- const allowedOrigins = [
- "http://localhost:5173",
- "http://localhost:3000",
- "https://fedkiit.com",
- "https://www.fedkiit.com",
- "https://awsapi2.fedkiit.com"
- ];
-
- if (allowedOrigins.includes(origin)) {
- callback(null, true);
- } else {
- callback(new Error('Not allowed by CORS'));
- }
+ // Allow all origins by returning the requesting origin
+ callback(null, origin || '*');
},
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
credentials: true,
From ad9e4c528593608335b85917efbda9545bc3f2ec Mon Sep 17 00:00:00 2001
From: Ritam-Vaskar
Date: Tue, 13 Jan 2026 01:54:07 +0530
Subject: [PATCH 03/15] Update CORS configuration to allow all origins with
credentials
---
index.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/index.js b/index.js
index 452100c..912a324 100644
--- a/index.js
+++ b/index.js
@@ -114,8 +114,8 @@ app.use(cookieParser());
app.use(cors({
origin: function (origin, callback) {
- // Allow all origins by returning the requesting origin
- callback(null, origin || '*');
+ // Allow all origins - return true to allow any origin with credentials
+ callback(null, true);
},
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
credentials: true,
From d8bdb3dcc919386c4a54208a9dbcd7057bf77030 Mon Sep 17 00:00:00 2001
From: Ritam-Vaskar
Date: Tue, 13 Jan 2026 02:07:01 +0530
Subject: [PATCH 04/15] Update CORS configuration to restrict allowed origins
---
index.js | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/index.js b/index.js
index 912a324..e038098 100644
--- a/index.js
+++ b/index.js
@@ -107,18 +107,29 @@ const PORT = process.env.PORT || 3000;
const frontendUrl = process.env.DOMAIN;
console.log("Frontend URL:", frontendUrl);
+
+const allowedOrigins = [
+ "https://www.fedkiit.com",
+ "https://fedkiit.com",
+ "http://localhost:5173",
+ "http://localhost:3000"
+];
+
// Middlewares
app.use(express.json({ limit: '16kb' }));
app.use(express.urlencoded({ extended: true, limit: "16kb" }));
app.use(cookieParser());
app.use(cors({
- origin: function (origin, callback) {
- // Allow all origins - return true to allow any origin with credentials
- callback(null, true);
- },
- methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
- credentials: true,
+ origin: (origin, callback) => {
+ if (!origin) return callback(null, true);
+ if (allowedOrigins.includes(origin)) {
+ return callback(null, origin);
+ }
+ callback(null, false);
+ },
+ credentials: true,
+ methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
}));
app.options('*', cors()); // handle preflight requests
From 67139829e2c71381fd5023daaddf851440f308d1 Mon Sep 17 00:00:00 2001
From: Ritam-Vaskar
Date: Tue, 13 Jan 2026 02:23:15 +0530
Subject: [PATCH 05/15] Refactor CORS middleware to use shared options for
normal and preflight requests
---
index.js | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/index.js b/index.js
index e038098..60d3e4f 100644
--- a/index.js
+++ b/index.js
@@ -115,23 +115,30 @@ const allowedOrigins = [
"http://localhost:3000"
];
-// Middlewares
-app.use(express.json({ limit: '16kb' }));
-app.use(express.urlencoded({ extended: true, limit: "16kb" }));
-app.use(cookieParser());
-
-app.use(cors({
+// Shared CORS options for both normal and preflight requests
+const corsOptions = {
origin: (origin, callback) => {
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
- return callback(null, origin);
+ return callback(null, true); // reflect request origin
}
callback(null, false);
},
credentials: true,
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
-}));
-app.options('*', cors()); // handle preflight requests
+ allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
+ exposedHeaders: ["Content-Length"]
+};
+
+// Middlewares
+app.use(express.json({ limit: '16kb' }));
+app.use(express.urlencoded({ extended: true, limit: "16kb" }));
+app.use(cookieParser());
+
+// Apply CORS for normal requests
+app.use(cors(corsOptions));
+// Ensure preflight responses use the same options (no wildcard when credentials)
+app.options('*', cors(corsOptions));
// const allowedOrigins = [
// "https://fedkiit.com",
From 5ff99df52a817fa5e7ac9407e7c85fc6048550a4 Mon Sep 17 00:00:00 2001
From: Ritam Vaskar
Date: Thu, 15 Jan 2026 00:35:28 +0530
Subject: [PATCH 06/15] Refactor Dockerfile for efficiency and Puppeteer config
Updated Dockerfile to streamline package installation and skip Chromium download.
---
Dockerfile | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index fad2978..88995f6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -33,8 +33,7 @@ RUN apt-get update && apt-get install -y \
xdg-utils \
wget \
--no-install-recommends && \
- apt-get clean && \
- rm -rf /var/lib/apt/lists/*
+ apt-get clean && rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
@@ -43,17 +42,20 @@ WORKDIR /app
ARG DATABASE_URL
ENV DATABASE_URL=${DATABASE_URL}
-# Copy package files and install deps
+# Skip Puppeteer Chromium download
+ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
+
+# Copy package files and install dependencies
COPY package*.json ./
-RUN npm install --omit=dev
+RUN npm ci --omit=dev
-# Copy source
+# Copy source code
COPY . .
# Make build.sh executable
RUN chmod +x ./build.sh
-# Expose app port
+# Expose port
EXPOSE 5000
# Start
From b65fa00552b72987b2327bc2d9890c77dd3ffcf5 Mon Sep 17 00:00:00 2001
From: Krishna Das
Date: Thu, 15 Jan 2026 01:59:39 +0530
Subject: [PATCH 07/15] feat: Migrate email from Nodemailer to Resend API with
fallback support
---
config/nodeMailer.js | 19 ++++++
config/resend.js | 7 ++
package-lock.json | 53 +++++++++++++++
package.json | 1 +
utils/email/nodeMailer.js | 133 ++++++++++++++++++++++++++++++++++++++
5 files changed, 213 insertions(+)
create mode 100644 config/resend.js
diff --git a/config/nodeMailer.js b/config/nodeMailer.js
index effadfd..bc74138 100644
--- a/config/nodeMailer.js
+++ b/config/nodeMailer.js
@@ -1,4 +1,12 @@
// config/nodeMailer.js
+// DEPRECATED: This file has been replaced by Resend API
+// See: config/resend.js and utils/email/nodeMailer.js for new implementation
+// Date: January 2026
+
+/* ============================================================
+ ORIGINAL NODEMAILER CONFIGURATION (COMMENTED OUT)
+ ============================================================
+
const nodemailer = require("nodemailer");
// Primary transporter (e.g., Gmail)
@@ -43,3 +51,14 @@ module.exports = {
tertiary: mailTransporterTertiary,
mailerSend: mailTransporterMailerSend,
};
+
+============================================================ */
+
+// Export empty object to prevent import errors during transition
+// This file is no longer used - Resend API is used instead
+module.exports = {
+ primary: null,
+ secondary: null,
+ tertiary: null,
+ mailerSend: null,
+};
diff --git a/config/resend.js b/config/resend.js
new file mode 100644
index 0000000..85eed5c
--- /dev/null
+++ b/config/resend.js
@@ -0,0 +1,7 @@
+// config/resend.js
+const { Resend } = require('resend');
+
+// Initialize Resend with API key from environment variable
+const resend = new Resend(process.env.RESEND_API_KEY);
+
+module.exports = { resend };
diff --git a/package-lock.json b/package-lock.json
index 06b43cd..a8b2efa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -41,6 +41,7 @@
"puppeteer-extra-plugin-stealth": "^2.11.2",
"qrcode": "^1.5.4",
"react-otp-input": "^3.1.1",
+ "resend": "^6.7.0",
"sharp": "^0.33.4",
"uuid": "^10.0.0",
"xlsx": "^0.18.5"
@@ -745,6 +746,12 @@
"node": ">=12"
}
},
+ "node_modules/@stablelib/base64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz",
+ "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==",
+ "license": "MIT"
+ },
"node_modules/@tootallnate/quickjs-emscripten": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
@@ -2379,6 +2386,12 @@
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT"
},
+ "node_modules/fast-sha256": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz",
+ "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==",
+ "license": "Unlicense"
+ },
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -4870,6 +4883,26 @@
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
+ "node_modules/resend": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/resend/-/resend-6.7.0.tgz",
+ "integrity": "sha512-2ZV0NDZsh4Gh+Nd1hvluZIitmGJ59O4+OxMufymG6Y8uz1Jgt2uS1seSENnkIUlmwg7/dwmfIJC9rAufByz7wA==",
+ "license": "MIT",
+ "dependencies": {
+ "svix": "1.84.1"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "peerDependencies": {
+ "@react-email/render": "*"
+ },
+ "peerDependenciesMeta": {
+ "@react-email/render": {
+ "optional": true
+ }
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -5309,6 +5342,16 @@
"node": ">=0.8"
}
},
+ "node_modules/standardwebhooks": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz",
+ "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==",
+ "license": "MIT",
+ "dependencies": {
+ "@stablelib/base64": "^1.0.0",
+ "fast-sha256": "^1.3.0"
+ }
+ },
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -5392,6 +5435,16 @@
"node": ">=4"
}
},
+ "node_modules/svix": {
+ "version": "1.84.1",
+ "resolved": "https://registry.npmjs.org/svix/-/svix-1.84.1.tgz",
+ "integrity": "sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "standardwebhooks": "1.0.0",
+ "uuid": "^10.0.0"
+ }
+ },
"node_modules/tar-fs": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
diff --git a/package.json b/package.json
index 5703c01..05733d2 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
"puppeteer-extra-plugin-stealth": "^2.11.2",
"qrcode": "^1.5.4",
"react-otp-input": "^3.1.1",
+ "resend": "^6.7.0",
"sharp": "^0.33.4",
"uuid": "^10.0.0",
"xlsx": "^0.18.5"
diff --git a/utils/email/nodeMailer.js b/utils/email/nodeMailer.js
index f0ed3fe..531217d 100644
--- a/utils/email/nodeMailer.js
+++ b/utils/email/nodeMailer.js
@@ -1,3 +1,134 @@
+// RESEND API IMPLEMENTATION WITH FALLBACK
+// Replaced Nodemailer with Resend API for email sending
+// Supports multiple sender domains with automatic fallback
+// Date: January 2026
+
+const { resend } = require("../../config/resend");
+
+/**
+ * Get list of sender emails from environment variables
+ * Supports EMAIL_FROM, EMAIL_FROM_2, EMAIL_FROM_3, etc.
+ * @returns {Array} Array of sender emails
+ */
+function getSenderEmails() {
+ const senders = [];
+
+ // Primary sender
+ if (process.env.EMAIL_FROM) {
+ senders.push(process.env.EMAIL_FROM);
+ }
+
+ // Secondary sender
+ if (process.env.EMAIL_FROM_2) {
+ senders.push(process.env.EMAIL_FROM_2);
+ }
+
+ // Tertiary sender
+ if (process.env.EMAIL_FROM_3) {
+ senders.push(process.env.EMAIL_FROM_3);
+ }
+
+ // Add more as needed (EMAIL_FROM_4, EMAIL_FROM_5, etc.)
+ for (let i = 4; i <= 10; i++) {
+ const envKey = `EMAIL_FROM_${i}`;
+ if (process.env[envKey]) {
+ senders.push(process.env[envKey]);
+ }
+ }
+
+ // Default fallback if no senders configured
+ if (senders.length === 0) {
+ senders.push('FED KIIT Compliance ');
+ }
+
+ return senders;
+}
+
+/**
+ * Send email using Resend API with automatic fallback to secondary domains
+ * @param {string} to - Recipient email address
+ * @param {string} subject - Email subject
+ * @param {string} htmlContent - HTML content of the email
+ * @param {string} textContent - Plain text content (optional, auto-generated from HTML if not provided)
+ * @param {Array} attachments - Array of attachment objects (optional)
+ * Format: [{ filename: 'file.pdf', content: Buffer }]
+ */
+async function sendMail(to, subject, htmlContent, textContent, attachments = []) {
+ // Get all configured sender emails
+ const senderEmails = getSenderEmails();
+
+ // Convert attachments to Resend format if present
+ const resendAttachments = attachments.map(att => ({
+ filename: att.filename,
+ content: att.content,
+ }));
+
+ // Build base email options
+ const baseEmailOptions = {
+ to: to,
+ subject: subject,
+ html: htmlContent,
+ text: textContent || htmlContent.replace(/<[^>]+>/g, ""),
+ reply_to: "fedkiit@gmail.com",
+ };
+
+ // Add attachments if present
+ if (resendAttachments.length > 0) {
+ baseEmailOptions.attachments = resendAttachments;
+ }
+
+ // Try each sender in sequence until one succeeds
+ let lastError = null;
+
+ for (let i = 0; i < senderEmails.length; i++) {
+ const senderEmail = senderEmails[i];
+ const isLastAttempt = i === senderEmails.length - 1;
+
+ try {
+ console.log(`[Resend] Attempting to send email using sender ${i + 1}: ${senderEmail}`);
+
+ const emailOptions = {
+ ...baseEmailOptions,
+ from: senderEmail,
+ };
+
+ const { data, error } = await resend.emails.send(emailOptions);
+
+ if (error) {
+ console.error(`[Resend] Sender ${i + 1} failed:`, error.message);
+ lastError = error;
+
+ if (!isLastAttempt) {
+ console.log(`[Resend] Trying fallback sender...`);
+ continue; // Try next sender
+ }
+ } else {
+ console.log(`[Resend] Email sent successfully via sender ${i + 1}:`, data);
+ return data;
+ }
+ } catch (error) {
+ console.error(`[Resend] Sender ${i + 1} threw exception:`, error.message);
+ lastError = error;
+
+ if (!isLastAttempt) {
+ console.log(`[Resend] Trying fallback sender...`);
+ continue; // Try next sender
+ }
+ }
+ }
+
+ // All senders failed
+ console.error("[Resend] All email senders failed. Last error:", lastError);
+ throw new Error(`Email sending failed after trying ${senderEmails.length} sender(s): ${lastError?.message || 'Unknown error'}`);
+}
+
+module.exports = { sendMail };
+
+
+/* ============================================================
+ ORIGINAL NODEMAILER IMPLEMENTATION (COMMENTED OUT)
+ ============================================================
+
const { primary, secondary, tertiary, mailerSend } = require("../../config/nodeMailer");
function sendMail(to, subject, htmlContent, textContent, attachments = []) {
@@ -58,3 +189,5 @@ function sendMail(to, subject, htmlContent, textContent, attachments = []) {
}
module.exports = { sendMail };
+
+============================================================ */
From ce59708227ca0698c422081363b78c62ecdea780 Mon Sep 17 00:00:00 2001
From: Krishna Das
Date: Thu, 15 Jan 2026 02:09:40 +0530
Subject: [PATCH 08/15] feat: Migrate email from Nodemailer to Resend API
---
config/resend.js | 17 +--
utils/email/nodeMailer.js | 221 ++++++++++++--------------------------
2 files changed, 78 insertions(+), 160 deletions(-)
diff --git a/config/resend.js b/config/resend.js
index 85eed5c..c85da16 100644
--- a/config/resend.js
+++ b/config/resend.js
@@ -1,7 +1,12 @@
-// config/resend.js
-const { Resend } = require('resend');
+/**
+ * RESEND EMAIL CONFIGURATION
+ * This file is kept for backward compatibility but is no longer used.
+ * Email sending is now handled directly in utils/email/nodeMailer.js
+ *
+ * The Resend clients are initialized directly in nodeMailer.js using:
+ * - RESEND_API_KEY + EMAIL_FROM (primary)
+ * - RESEND_API_KEY_2 + EMAIL_FROM_2 (fallback)
+ */
-// Initialize Resend with API key from environment variable
-const resend = new Resend(process.env.RESEND_API_KEY);
-
-module.exports = { resend };
+// This file is deprecated - Resend clients are created in nodeMailer.js
+module.exports = {};
diff --git a/utils/email/nodeMailer.js b/utils/email/nodeMailer.js
index 531217d..ca30675 100644
--- a/utils/email/nodeMailer.js
+++ b/utils/email/nodeMailer.js
@@ -1,70 +1,45 @@
-// RESEND API IMPLEMENTATION WITH FALLBACK
-// Replaced Nodemailer with Resend API for email sending
-// Supports multiple sender domains with automatic fallback
-// Date: January 2026
-
-const { resend } = require("../../config/resend");
-
/**
- * Get list of sender emails from environment variables
- * Supports EMAIL_FROM, EMAIL_FROM_2, EMAIL_FROM_3, etc.
- * @returns {Array} Array of sender emails
+ * RESEND EMAIL SERVICE
+ * Production-ready email sending with 2-domain fallback support
+ *
+ * Environment Variables Required:
+ * - RESEND_API_KEY: API key for primary domain
+ * - EMAIL_FROM: Primary sender (e.g., "FED KIIT ")
+ * - RESEND_API_KEY_2: API key for fallback domain
+ * - EMAIL_FROM_2: Fallback sender (e.g., "FED KIIT ")
*/
-function getSenderEmails() {
- const senders = [];
- // Primary sender
- if (process.env.EMAIL_FROM) {
- senders.push(process.env.EMAIL_FROM);
- }
-
- // Secondary sender
- if (process.env.EMAIL_FROM_2) {
- senders.push(process.env.EMAIL_FROM_2);
- }
-
- // Tertiary sender
- if (process.env.EMAIL_FROM_3) {
- senders.push(process.env.EMAIL_FROM_3);
- }
-
- // Add more as needed (EMAIL_FROM_4, EMAIL_FROM_5, etc.)
- for (let i = 4; i <= 10; i++) {
- const envKey = `EMAIL_FROM_${i}`;
- if (process.env[envKey]) {
- senders.push(process.env[envKey]);
- }
- }
+const { Resend } = require("resend");
- // Default fallback if no senders configured
- if (senders.length === 0) {
- senders.push('FED KIIT Compliance ');
- }
+// Initialize Resend clients for both domains
+const resendPrimary = process.env.RESEND_API_KEY
+ ? new Resend(process.env.RESEND_API_KEY)
+ : null;
- return senders;
-}
+const resendSecondary = process.env.RESEND_API_KEY_2
+ ? new Resend(process.env.RESEND_API_KEY_2)
+ : null;
/**
- * Send email using Resend API with automatic fallback to secondary domains
+ * Send email using Resend API with automatic fallback
+ * Tries primary domain first, falls back to secondary if primary fails
+ *
* @param {string} to - Recipient email address
* @param {string} subject - Email subject
* @param {string} htmlContent - HTML content of the email
- * @param {string} textContent - Plain text content (optional, auto-generated from HTML if not provided)
- * @param {Array} attachments - Array of attachment objects (optional)
- * Format: [{ filename: 'file.pdf', content: Buffer }]
+ * @param {string} textContent - Plain text content (optional, auto-generated from HTML)
+ * @param {Array} attachments - Array of attachments [{filename, content}]
+ * @returns {Promise
Hi {{leaderName}},
@@ -121,8 +121,8 @@
Team Join Request
This request will expire in {{expiryHours}} hours.
@@ -132,7 +132,7 @@
Team Join Request