npm install resend
// services/email.js
const { Resend } = require('resend');
const resend = new Resend(
process.env.RESEND_API_KEY);
async function sendEmail({
to, subject, html
}) {
const { data, error } =
await resend.emails.send({
from: 'PixelCraft <noreply@pixelcraft.app>',
to,
subject,
html,
});
if (error) throw error;
return data;
}
module.exports = { sendEmail };
const crypto = require('crypto');
router.post('/auth/forgot-password',
validate(forgotPasswordSchema),
async (req, res) => {
const user = await User.findOne({
email: req.body.email });
// Always respond the same way
// (don't leak user existence)
if (!user) {
return res.json({
message: 'If an account exists, ' +
'we sent a reset email.' });
}
// Generate secure token
const token =
crypto.randomBytes(32).toString('hex');
const tokenHash =
crypto.createHash('sha256')
.update(token).digest('hex');
// Store hash + expiry in DB
user.resetTokenHash = tokenHash;
user.resetTokenExpires =
new Date(Date.now() + 60 * 60 * 1000);
await user.save();
// Send email with raw token
const resetUrl =
`${process.env.FRONTEND_URL}` +
`/reset-password?token=${token}`;
await sendEmail({
to: user.email,
subject: 'Reset your password',
html: resetPasswordTemplate({
name: user.name, resetUrl }),
});
res.json({
message: 'If an account exists, ' +
'we sent a reset email.' });
});
router.post('/auth/reset-password',
validate(resetPasswordSchema),
async (req, res) => {
const { token, newPassword } = req.body;
// Hash the provided token
const tokenHash =
crypto.createHash('sha256')
.update(token).digest('hex');
// Find user with matching
// unexpired token
const user = await User.findOne({
resetTokenHash: tokenHash,
resetTokenExpires: { $gt: new Date() },
});
if (!user) {
return res.status(400).json({
error: 'Invalid or expired token'
});
}
// Update password
user.password =
await bcrypt.hash(newPassword, 12);
// Invalidate the token
user.resetTokenHash = undefined;
user.resetTokenExpires = undefined;
await user.save();
res.json({
message: 'Password reset successful'
});
});
function resetPasswordTemplate({
name, resetUrl
}) {
return `
<div style="
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
">
<h2 style="color: #1a1a2e;">
Reset Your Password
</h2>
<p>Hi ${name},</p>
<p>Click the button below to
reset your password.
This link expires in 1 hour.</p>
<a href="${resetUrl}" style="
display: inline-block;
background: #e94560;
color: white;
padding: 12px 24px;
text-decoration: none;
border-radius: 4px;
margin: 16px 0;
">Reset Password</a>
<p style="color: #666;
font-size: 14px;">
If you didn't request this,
ignore this email.
</p>
</div>
`;
}
git switch -c feature/PIXELCRAFT-070-password-reset
git add server/ src/
git commit -m "Add password reset flow with email (PIXELCRAFT-070)"
git push origin feature/PIXELCRAFT-070-password-reset
# PR → Review → Merge → Close ticket ✅
Email is the oldest and most critical internet protocol still in use.
SMTP was defined in 1982 — before the web existed. It's still how every email is delivered.