forked from StellarSplit/StellarSplit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathemail.service.ts
More file actions
151 lines (135 loc) · 4.01 KB
/
Copy pathemail.service.ts
File metadata and controls
151 lines (135 loc) · 4.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { Injectable, Logger, OnModuleInit } from "@nestjs/common";
import { InjectQueue } from "@nestjs/bull";
import { Queue } from "bull";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { ConfigService } from "@nestjs/config";
import * as nodemailer from "nodemailer";
import * as handlebars from "handlebars";
import * as fs from "fs";
import * as path from "path";
import { User } from "../entities/user.entity";
const SUBJECT_MAP: Record<string, string> = {
invitation: "Invitation to join a new Split on StellarSplit",
reminder: "Payment Reminder for StellarSplit",
confirmation: "Payment Received Confirmation",
completed: "Split Completed!",
archive_warning: "Your split will be archived in 7 days",
};
@Injectable()
export class EmailService implements OnModuleInit {
private readonly logger = new Logger(EmailService.name);
private transporter!: nodemailer.Transporter;
private useDevStub = false;
constructor(
@InjectQueue("email_queue") private readonly emailQueue: Queue,
@InjectRepository(User) private readonly userRepository: Repository<User>,
private readonly configService: ConfigService,
) {}
onModuleInit(): void {
const smtpHost = this.configService.get<string>("SMTP_HOST");
if (!smtpHost) {
this.logger.warn(
"SMTP_HOST is not configured. Emails will be logged to stdout instead of sent.",
);
this.transporter = nodemailer.createTransport({ jsonTransport: true });
this.useDevStub = true;
return;
}
this.transporter = nodemailer.createTransport({
host: smtpHost,
port: this.configService.get<number>("SMTP_PORT", 587),
auth: {
user: this.configService.get<string>("SMTP_USER"),
pass: this.configService.get<string>("SMTP_PASSWORD"),
},
});
}
async sendTemplatedEmail(
to: string,
type: string,
context: Record<string, unknown>,
): Promise<void> {
const templatePath = path.join(__dirname, "templates", `${type}.hbs`);
const source = fs.readFileSync(templatePath, "utf8");
const template = handlebars.compile(source);
const html = template(context);
const result = await this.transporter.sendMail({
from: '"StellarSplit" <noreply@stellarsplit.com>',
to,
subject: SUBJECT_MAP[type] || "StellarSplit Notification",
html,
});
if (this.useDevStub) {
this.logger.warn(
`[DEV EMAIL STUB] Email to ${to} (${type}): ${result.message?.toString() ?? JSON.stringify(result)}`,
);
}
}
async sendInvitation(
to: string,
context: {
inviterName: string;
splitDescription: string;
amount: number;
joinLink: string;
},
) {
await this.emailQueue.add("sendEmail", {
to,
type: "invitation",
context,
});
}
async sendPaymentReminder(
to: string,
context: {
participantName: string;
splitDescription: string;
amountDue: number;
paymentLink: string;
},
) {
await this.emailQueue.add("sendEmail", {
to,
type: "reminder",
context,
});
}
async sendPaymentConfirmation(
to: string,
context: { amount: number; splitDescription: string; txHash: string },
) {
await this.emailQueue.add("sendEmail", {
to,
type: "confirmation",
context,
});
}
async sendSplitCompleted(
to: string,
context: { splitDescription: string; totalAmount: number },
) {
await this.emailQueue.add("sendEmail", {
to,
type: "completed",
context,
});
}
async sendArchiveWarning(
to: string,
context: { splitDescription: string; archiveDate: string },
) {
await this.emailQueue.add("sendEmail", {
to,
type: "archive_warning",
context,
});
}
async updatePreferences(userId: string, preferences: any) {
await this.userRepository.update(userId, { emailPreferences: preferences });
}
async getUser(userId: string) {
return this.userRepository.findOne({ where: { id: userId } });
}
}