Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a405019
feat(email): add email template and send API route
stevenphanny Jan 19, 2026
fc654b8
chore(MAC-239): small tailwind change
stevenphanny Jan 19, 2026
1358792
feat(email): add email template and send API route
stevenphanny Jan 19, 2026
392f614
chore(MAC-239): small tailwind change
stevenphanny Jan 19, 2026
403e054
Merge branch 'mac-239-setup-resend-emails' of https://github.com/mona…
stevenphanny Jan 19, 2026
aa9a659
feat(MAC-239): setup basic form for testing and temporarily using ono…
stevenphanny Jan 20, 2026
f080e06
feat(MAC-239): created email-template for nicer gmail view
stevenphanny Jan 22, 2026
2ac7c7c
fix(MAC-239): removed phone fields from form
stevenphanny Jan 22, 2026
3ddd1c1
fix(MAC-239): custom error handling because default look ugly
stevenphanny Jan 22, 2026
9840960
feat(MAC-239): setup basic form for testing and temporarily using ono…
stevenphanny Jan 20, 2026
f3dda9b
feat(MAC-239): created email-template for nicer gmail view
stevenphanny Jan 22, 2026
1f7cdb3
fix(MAC-239): removed phone fields from form
stevenphanny Jan 22, 2026
5b9a4bb
fix(MAC-239): custom error handling because default look ugly
stevenphanny Jan 22, 2026
8323bd4
Merge branch 'mac-239-setup-resend-emails' of https://github.com/mona…
stevenphanny Jan 23, 2026
6f31601
Merge remote-tracking branch 'origin' into mac-239-setup-resend-emails
stevenphanny Jan 25, 2026
edf5957
fix(MAC-239): fix colouring to match new colours
stevenphanny Jan 25, 2026
b908658
fix(MAC-239): .env.example was wrong
stevenphanny Jan 25, 2026
37a08ec
Update app/api/send/route.ts
stevenphanny Jan 25, 2026
b01440b
Update package.json
stevenphanny Jan 25, 2026
7bf9e03
Update components/ContactPageClient.tsx
stevenphanny Jan 25, 2026
ef48664
Update .env.example
stevenphanny Jan 25, 2026
e585590
Update components/ContactPageClient.tsx
stevenphanny Jan 25, 2026
e679c5f
fix(MAC-239): adding resend api key check and fixing css on contact page
stevenphanny Jan 25, 2026
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: 4 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ NEXT_PUBLIC_SANITY_PROJECT_ID=your_project_id
NEXT_PUBLIC_SANITY_DATASET=production
NEXT_PUBLIC_SANITY_API_VERSION=2024-01-18

# Sanity CMS Configuration (Sanity CLI: dev, deploy)
SANITY_STUDIO_PROJECT_ID=your_project_id
SANITY_STUDIO_DATASET=production
SANITY_STUDIO_API_VERSION=2024-01-18
# Resend Configuration
# Required: used to send contact form emails via Resend
RESEND_API_KEY=your_resend_api_key

100 changes: 100 additions & 0 deletions app/api/send/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { EmailTemplate } from '../../../components/EmailTemplate';
import { Resend } from 'resend';

// Validate RESEND_API_KEY is configured
const apiKey = process.env.RESEND_API_KEY;
if (!apiKey || apiKey.trim().length === 0) {
throw new Error(
'RESEND_API_KEY is not configured. Please set the RESEND_API_KEY environment variable.'
);
}

const resend = new Resend(apiKey);

function isNonEmptyString(value: unknown): value is string {
return typeof value === 'string' && value.trim().length > 0;
}

function isValidEmail(value: unknown): value is string {
if (typeof value !== 'string') return false;
const email = value.trim();
// Basic email pattern; not exhaustive but sufficient for simple validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return email.length > 0 && emailRegex.test(email);
}

export async function POST(req: Request) {
try {
// Get the form data from the request
const body = await req.json();

if (!body || typeof body !== 'object') {
return Response.json(
{ error: 'Invalid request body; expected JSON object.' },
{ status: 400 },
);
}

const {
name,
emailAddress,
subject,
message,
} = body as {
name?: unknown;
emailAddress?: unknown;
subject?: unknown;
message?: unknown;
};

if (!isNonEmptyString(name)) {
return Response.json(
{ error: 'Field "name" is required and must be a non-empty string.' },
{ status: 400 },
);
}

if (!isValidEmail(emailAddress)) {
return Response.json(
{ error: 'Field "emailAddress" is required and must be a valid email address.' },
{ status: 400 },
);
}

if (!isNonEmptyString(message)) {
return Response.json(
{ error: 'Field "message" is required and must be a non-empty string.' },
{ status: 400 },
);
}

const normalizedSubject =
typeof subject === 'string' && subject.trim().length > 0
? subject
: 'New Message from Monash Coding Site';

const { data, error } = await resend.emails.send({
from: 'noreply@monashcoding.com',
// to: 'coding@monashclubs.org',
to: 'projects@monashcoding.com',
replyTo: (emailAddress as string).trim(), // User's email will be set as reply-to
subject: normalizedSubject,
react: EmailTemplate({
name: (name as string).trim(),
emailAddress: (emailAddress as string).trim(),
subject: normalizedSubject,
message: (message as string).trim(),
}),
});

if (error) {
console.error('Resend API error:', error);
return Response.json({ error }, { status: 500 });
}

return Response.json(data);
} catch (error) {
console.error('Catch error:', error);
return Response.json({ error: String(error) }, { status: 500 });
}
}
Loading