Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@
# MONGODB_URI=mongodb://localhost:27017/iiitl-alumni

# MongoDB Atlas (production example)
# MONGODB_URI=mongodb+srv://<username>:<password>@cluster.mongodb.net/iiitl
# MONGODB_URI=mongodb+srv://<username>:<password>@cluster.mongodb.net/iiitl

# Mailgun Configuration
MAILGUN_API_KEY=""
MAILGUN_DOMAIN=""
# MAILGUN_URL="https://api.eu.mailgun.net" # Uncomment if using an EU region domain
EMAIL_FROM="no-reply@yourdomain.com"
1 change: 1 addition & 0 deletions .student-hub-ref
Submodule .student-hub-ref added at 3efe55
2 changes: 2 additions & 0 deletions lib/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ if (!cached) {
}

export async function connectDB() {
const MONGODB_URI = process.env.MONGODB_URI;

if (!MONGODB_URI) {
throw new Error("Please define the MONGODB_URI environment variable");
}
Expand Down
88 changes: 88 additions & 0 deletions lib/email/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import FormData from 'form-data';
import Mailgun from 'mailgun.js';
import EmailLog from '../../models/EmailLog';
import { connectDB } from '../db';

interface EmailOptions {
to: string;
subject: string;
text: string;
html?: string;
}

function validateMailgunEnvVars(): boolean {
const requiredVars = ['MAILGUN_API_KEY', 'MAILGUN_DOMAIN'];
const missingVars = requiredVars.filter((varName) => !process.env[varName]);

if (missingVars.length > 0) {
console.warn(`Missing Mailgun environment variables: ${missingVars.join(', ')}`);
return false;
}
return true;
}

let mailgunClient: ReturnType<Mailgun['client']> | null = null;

function getMailgunClient() {
if (!mailgunClient && validateMailgunEnvVars()) {
const mailgun = new Mailgun(FormData);
mailgunClient = mailgun.client({
username: 'api',
key: process.env.MAILGUN_API_KEY || '',
url: process.env.MAILGUN_URL || 'https://api.mailgun.net', // allows EU domains
});
}
return mailgunClient;
}

export async function sendEmail({ to, subject, text, html }: EmailOptions): Promise<boolean> {
const mg = getMailgunClient();

if (!mg || !process.env.MAILGUN_DOMAIN) {
console.error('Mailgun client not initialized or domain missing');
return false;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Ensure from address is in proper format
const fromAddress = process.env.EMAIL_FROM || `no-reply@${process.env.MAILGUN_DOMAIN}`;

try {
const data = await mg.messages.create(process.env.MAILGUN_DOMAIN, {
from: fromAddress,
to: [to],
subject,
text,
html: html || text,
});

console.log('Email sent successfully via Mailgun:', data.id);
await logEmail(to, subject, 'sent', data.id);
return true;
} catch (error: unknown) {
const err = error as { message?: string };
console.error('Mailgun error details:', err?.message);
await logEmail(to, subject, 'failed', undefined, err?.message);
return false;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
}

async function logEmail(
to: string,
subject: string,
status: 'sent' | 'failed',
messageId?: string,
errorDetails?: string
) {
try {
await connectDB();
await EmailLog.create({
to,
subject,
status,
messageId,
errorDetails,
});
} catch (dbError) {
console.error('Failed to log email to database:', dbError);
}
}
41 changes: 41 additions & 0 deletions models/EmailLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import mongoose, { Schema, Document, Model } from 'mongoose';

export interface IEmailLog extends Document {
to: string;
subject: string;
status: 'sent' | 'failed';
messageId?: string;
errorDetails?: string;
createdAt: Date;
updatedAt: Date;
}

const EmailLogSchema: Schema<IEmailLog> = new Schema<IEmailLog>(
{
to: {
type: String,
required: [true, 'Recipient email is required'],
},
subject: {
type: String,
required: [true, 'Email subject is required'],
},
status: {
type: String,
enum: ['sent', 'failed'],
required: [true, 'Email status is required'],
},
messageId: {
type: String,
},
errorDetails: {
type: String,
},
},
{ timestamps: true }
);
Comment thread
DistantMyth marked this conversation as resolved.

const EmailLog: Model<IEmailLog> =
mongoose.models.EmailLog || mongoose.model<IEmailLog>('EmailLog', EmailLogSchema);

export default EmailLog;
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
},
"dependencies": {
"dotenv": "^17.4.1",
"form-data": "^4.0.5",
"mailgun.js": "^12.7.1",
"mongoose": "^9.4.1",
"next": "16.2.3",
"react": "19.2.4",
Expand All @@ -24,6 +26,7 @@
"eslint": "^9",
"eslint-config-next": "16.2.3",
"tailwindcss": "^4",
"tsx": "^4.21.0",
"typescript": "^5"
}
}
Loading