Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.pyqdeck.in/llms.txt

Use this file to discover all available pages before exploring further.

Overview

PyqDeck uses Resend for email delivery and Mailgen for HTML email template generation. The system is implemented but not yet wired into the application’s routes/controllers.

Configuration

Environment Variables

VariableExamplePurpose
RESEND_API_KEYre_abc123...Resend API authentication key
MAIL_FROMonboarding@resend.devSender “from” address
APP_NAMEPyqDeckProduct name displayed in email templates
APP_URLhttps://pyqdeck.inProduct link in email footer

Setup (backend/src/config/mail.config.js)

import Resend from 'resend';
import Mailgen from 'mailgen';
import config from './index.js';

let resendClient = null;
let mailGenerator = null;

if (config.mail.apiKey) {
  resendClient = new Resend(config.mail.apiKey);
}

mailGenerator = new Mailgen({
  theme: 'default',
  product: {
    name: config.mail.appName,
    link: process.env.APP_URL || 'http://localhost:3000',
  },
});
Key design decision: If no RESEND_API_KEY is set, resendClient is null and all email functions become no-ops. This allows the app to run locally without email configuration.

Email Types

Three email functions are available in backend/src/services/mail.service.js:

1. Verification Email (OTP)

sendVerificationEmail(email, otp)
PropertyValue
Subject"Verify Your Email"
Content”Use the following OTP to verify your email:“
Button colorGreen (#22BC66)
DisplayOTP code as button text

2. Welcome Email

sendWelcomeEmail(email, name)
PropertyValue
Subject"Welcome to Our Platform"
Content"Welcome ${name}! We're glad to have you on board."
Outro”Feel free to reach out if you have any questions.”
CTALink to the app via APP_URL

3. Password Reset Email (OTP)

sendPasswordResetEmail(email, otp)
PropertyValue
Subject"Reset Your Password"
Content”Use the following OTP to reset your password:“
Button colorRed (#FF0000)
DisplayOTP code as button text

How Templates Work

There are no static HTML template files. All email templates are generated dynamically via Mailgen’s API at runtime.

OTP Emails (Action-based)

const emailContent = mailGenerator.generate({
  body: {
    action: {
      instructions: 'Use the following OTP to verify your email:',
      button: {
        color: '#22BC66',
        text: otp,
      },
    },
  },
});
Mailgen renders this into a responsive HTML email with the OTP displayed as a prominent action button.

Welcome Email (Text-based)

const emailContent = mailGenerator.generate({
  body: {
    intro: `Welcome ${name}! We're glad to have you on board.`,
    outro: 'Feel free to reach out if you have any questions.',
  },
});

Sending the Email

await resendClient.emails.send({
  from: config.mail.from,
  to: email,
  subject: 'Verify Your Email',
  html: emailContent.html,
  text: emailContent.plaintext,
});
Each email is sent with both HTML and plain text versions for maximum compatibility.

Error Handling

All three functions follow the same pattern:
export async function sendVerificationEmail(email, otp) {
  if (!resendClient) return;  // Graceful no-op

  try {
    // Generate template + send
    const emailContent = mailGenerator.generate({ ... });
    await resendClient.emails.send({ ... });
    loggerService.info(`Verification email sent to ${email}`);
  } catch (error) {
    loggerService.error(`Failed to send verification email: ${error.message}`);
    throw error;  // Don't swallow - caller should handle
  }
}
  • No API key → returns immediately (no error)
  • Send fails → logs error + throws (caller must handle)
  • Success → logs confirmation

Current Status: Not Yet Wired In

The email service functions are implemented but not called anywhere in the application. No controller, route, or service imports from mail.service.js.

Where They Should Be Connected

Email TypeSuggested Trigger Point
Verification OTPAfter user signs up, before Clerk confirms email
Welcome EmailAfter user.created Clerk webhook event
Password Reset OTPWhen user requests password reset

Example: Wiring into Clerk Webhook

// backend/src/routes/webhook.js
import { sendWelcomeEmail } from '../services/mail.service.js';

router.post('/webhooks/clerk', async (req, res) => {
  const event = verifyWebhook(req);

  if (event.type === 'user.created') {
    const { email_addresses, first_name } = event.data;
    const primaryEmail = email_addresses[0]?.email_address;

    if (primaryEmail) {
      await sendWelcomeEmail(primaryEmail, first_name || 'there');
    }
  }

  res.json({ received: true });
});

Resend Setup

Getting an API Key

  1. Sign up at resend.com
  2. Create an API key from the dashboard
  3. Free tier: 3,000 emails/month, 100 emails/day

Domain Verification

For production, verify your domain in Resend to:
  • Remove the “via resend.net” footer
  • Use your own from address (e.g., noreply@pyqdeck.in)
  • Improve email deliverability
Add the provided DNS records (SPF, DKIM, DMARC) to your domain registrar.

Testing Locally

Resend provides a test mode where emails are logged but not actually sent. Use the test API key (re_123456789) for local development.

Testing

The email service has full test coverage:

Happy Path Tests

// tests/services/mail.service.test.js
it('should send verification email successfully', async () => {
  mockResend.emails.send.mockResolvedValue({ id: 'test-id' });
  await sendVerificationEmail('user@test.com', '123456');
  expect(mockResend.emails.send).toHaveBeenCalled();
});

Error Path Tests

it('should throw error when sending fails', async () => {
  mockResend.emails.send.mockRejectedValue(new Error('API error'));
  await expect(sendVerificationEmail('user@test.com', '123456'))
    .rejects.toThrow('API error');
});

No-Config Tests

// tests/services/mail.no-config.test.js
it('should return early when resendClient is null', async () => {
  const result = await sendVerificationEmail('user@test.com', '123456');
  expect(result).toBeUndefined();  // No-op
});

Future Enhancements

When wiring the email system into the app, consider:
  1. Email queue — Use a queue (Bull, Redis) to handle email sending asynchronously
  2. Email templates — Create custom Mailgen themes or switch to a more flexible template engine for complex emails
  3. Email analytics — Track open rates, click rates via Resend’s analytics API
  4. Additional email types — Notification emails, study reminders, paper update alerts
  5. OTP storage — Store OTPs with expiry in Redis or MongoDB for verification

Next Steps