Email yuborish va User Activation
Muammodan boshlaylik
Foydalanuvchi ro'yxatdan o'tdi, lekin email manzili to'g'rimi? Kimdir boshqaning emailini kiritgan bo'lishi mumkin. Yoki tasodifan xato yozgan.
Ko'pchilik ilovalar ro'yxatdan o'tishdan keyin email tasdiqlashni talab qiladi:
- Foydalanuvchi email kiritadi
- Unga maxsus link yuboriladi
- U linkni bosadi — hisob faollashadi
Bu ham xavfsizlik, ham spam oldini olish.
Nodemailer nima?
Nodemailer — Node.js da email yuborish uchun eng mashhur kutubxona. Gmail, Outlook, yoki o'z SMTP serveringiz bilan ishlaydi.
npm install nodemailer
npm install -D @types/nodemailer
Email Module yaratish
// email/email.service.ts
import { Injectable } from '@nestjs/common';
import * as nodemailer from 'nodemailer';
@Injectable()
export class EmailService {
private transporter: nodemailer.Transporter;
constructor() {
this.transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
}
async send(to: string, subject: string, html: string) {
await this.transporter.sendMail({
from: `"MyApp" <${process.env.SMTP_USER}>`,
to,
subject,
html,
});
}
}
Gmail bilan sozlash (development uchun)
# .env
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=yourname@gmail.com
SMTP_PASS=your_app_password # Google App Password (ikki bosqichli tekshiruv yoqilgan bo'lsa)
⚠️ Gmail parolini emas, App Password ishlating. Google Account → Security → App Passwords dan oling.
Production da: Resend, SendGrid, Mailgun kabi maxsus xizmatlar ishlatiladi. Arzonroq va ishonchli.
HTML shablonlar
Oddiy matn emas, chiroyli email:
// email/templates/welcome.template.ts
export const welcomeTemplate = (name: string, activationLink: string) => `
<!DOCTYPE html>
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2>Salom, ${name}!</h2>
<p>Ro'yxatdan o'tganingiz uchun rahmat.</p>
<p>Hisobingizni faollashtirish uchun quyidagi tugmani bosing:</p>
<a href="${activationLink}"
style="background: #4F46E5; color: white; padding: 12px 24px;
text-decoration: none; border-radius: 6px; display: inline-block;">
Hisobni faollashtirish
</a>
<p>Link 24 soat amal qiladi.</p>
</body>
</html>
`;
User Activation — to'liq oqim
1. Ro'yxatdan o'tishda token yaratish
// users/users.service.ts
import { randomBytes } from 'crypto';
@Injectable()
export class UsersService {
constructor(
private userRepository: UserRepository,
private emailService: EmailService,
) {}
async register(dto: RegisterDto) {
// Token yaratish
const activationToken = randomBytes(32).toString('hex');
const tokenExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 soat
// Foydalanuvchi yaratish (hali faol emas)
const user = await this.userRepository.save({
...dto,
password: await bcrypt.hash(dto.password, 10),
isActive: false, // ← faol emas
activationToken, // ← token saqlangan
activationTokenExpiry: tokenExpiry,
});
// Email yuborish
const link = `${process.env.APP_URL}/auth/activate/${activationToken}`;
await this.emailService.send(
user.email,
'Hisobingizni faollashtiring',
welcomeTemplate(user.name, link),
);
return { message: 'Emailingizni tekshiring' };
}
}
2. Token orqali faollashtirish
async activate(token: string) {
const user = await this.userRepository.findOne({
where: { activationToken: token },
});
if (!user) {
throw new BadRequestException('Token noto\'g\'ri');
}
if (user.activationTokenExpiry < new Date()) {
throw new BadRequestException('Token muddati o\'tgan');
}
await this.userRepository.update(user.id, {
isActive: true,
activationToken: null, // ← bir marta ishlatildi, o'chirildi
activationTokenExpiry: null,
});
return { message: 'Hisob faollashtirildi' };
}
3. Controller
@Controller('auth')
export class AuthController {
@Get('activate/:token')
activate(@Param('token') token: string) {
return this.usersService.activate(token);
}
@Post('resend-activation')
resendActivation(@Body('email') email: string) {
return this.usersService.resendActivation(email);
}
}
Parolni tiklash (Reset Password)
Xuddi shu pattern, boshqa maqsad uchun:
async forgotPassword(email: string) {
const user = await this.userRepository.findOne({ where: { email } });
if (!user) return; // ← email topilmasa ham xato bermang (xavfsizlik)
const token = randomBytes(32).toString('hex');
await this.userRepository.update(user.id, {
resetToken: token,
resetTokenExpiry: new Date(Date.now() + 60 * 60 * 1000), // 1 soat
});
const link = `${process.env.FRONTEND_URL}/reset-password?token=${token}`;
await this.emailService.send(
email,
'Parolni tiklash',
resetPasswordTemplate(link),
);
}
Tez-tez uchraydigan xatolar
1. Email ketmayapti, xato yo'q
Sababi: SMTP sozlamalari noto'g'ri, lekin catch qilinmayapti.
Yechim: try/catch qo'shing va logging ishlating.
2. Gmail "Less secure app" xatosi Sababi: Gmail oddiy parol bilan ulanishni bloklaydi. Yechim: Google App Password ishlating (ikki bosqichli tekshiruv yoqiq bo'lishi kerak).
3. Spam papkasiga tushyapti Sababi: SPF, DKIM, DMARC yozuvlari sozlanmagan. Yechim: Production da SendGrid/Resend kabi xizmatlar ishlating — ular domain tekshiruvini osonlashtiradi.
Xulosa
Eslab qoling:
- Nodemailer — email yuborish uchun
- Activation token —
randomBytes(32)bilan yaratiladi - Token bir martalik, vaqt cheklangan bo'lishi kerak
- Faollashtirilgandan keyin token o'chiriladi
- Production da Gmail emas — SendGrid, Resend, Mailgun ishlating
Keyingi dars: 49-dars: Telegram Bot — asoslar