@lenne.tech/nest-server 11.43.5 → 11.43.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ # Migration Guide: 11.43.5 → 11.43.6
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ | -------------------- | ------------------------------------------------------------------------------------------------------- |
7
+ | **New Features** | Optional Reply-To for outgoing mail: `email.defaultReplyTo` and a per-call `replyTo` (§1) |
8
+ | **Breaking Changes** | None |
9
+ | **Migration Effort** | None. Set `email.defaultReplyTo` only if answers to your mail should go to another address (§1) |
10
+
11
+ ## Quick Migration
12
+
13
+ ```bash
14
+ pnpm add @lenne.tech/nest-server@11.43.6
15
+ pnpm run update # nest-server-starter based projects
16
+ ```
17
+
18
+ Vendor-mode projects update the core to the tag `11.43.6`: `/lt-dev:backend:update-nest-server-core --target 11.43.6`.
19
+ Without `--target` the updater syncs to the latest tag, which is on the 12.x line.
20
+
21
+ ## 1. Reply-To for outgoing mail
22
+
23
+ `EmailService.sendMail()` can set a `Reply-To` header, so a recipient's answer goes to another address than the
24
+ sender, e.g. a shared support mailbox while mail goes out from `noreply@…`. Nothing changes until you set it.
25
+
26
+ ```typescript
27
+ // config.env.ts
28
+ email: {
29
+ defaultSender: { email: 'noreply@example.com', name: 'Example' },
30
+ defaultReplyTo: 'support@example.com', // or '"Example Support" <support@example.com>'
31
+ },
32
+ ```
33
+
34
+ As an environment variable:
35
+
36
+ ```bash
37
+ NSC__EMAIL__DEFAULT_REPLY_TO=support@example.com
38
+ ```
39
+
40
+ A call can override it, or send one mail without it:
41
+
42
+ ```typescript
43
+ await this.emailService.sendMail(to, subject, { replyTo: 'sales@example.com', htmlTemplate, templateData });
44
+ await this.emailService.sendMail(to, subject, { replyTo: false, htmlTemplate, templateData });
45
+ ```
46
+
47
+ The default applies to every mail the service sends over SMTP, Better-Auth's verification and password-reset mail
48
+ included. Mail sent through Brevo (`brevo.apiKey`) or Mailjet is not affected: their templates carry their own
49
+ reply-to. A value that is not a string is ignored with one warning rather than stopping the mail.
50
+
51
+ ## Module Documentation
52
+
53
+ - Configuration reference: `email.defaultReplyTo` in `FRAMEWORK-API.md` (`IServerOptions.email`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.43.5",
3
+ "version": "11.43.6",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -2510,6 +2510,17 @@ export interface IServerOptions {
2510
2510
  * SMTP and template configuration for sending emails
2511
2511
  */
2512
2512
  email?: {
2513
+ /**
2514
+ * Default Reply-To of every mail `EmailService.sendMail()` sends (11.43.6+): where a recipient's answer goes when it
2515
+ * should not reach the sender address, e.g. a shared support mailbox. An address, or `"Name" <address>`.
2516
+ *
2517
+ * Absent (the default) → no Reply-To header, exactly as before. A call overrides it with `replyTo`, and
2518
+ * `replyTo: false` sends that one mail without it. Set it through `NSC__EMAIL__DEFAULT_REPLY_TO`. Applies to the
2519
+ * SMTP path (nodemailer, `email.smtp`): Brevo and Mailjet templates carry their own reply-to.
2520
+ * @default undefined
2521
+ */
2522
+ defaultReplyTo?: string;
2523
+
2513
2524
  /**
2514
2525
  * Data for default sender
2515
2526
  */
@@ -28,6 +28,9 @@ export class EmailService implements OnModuleDestroy {
28
28
  /** Once-per-process latch for the SMTP TLS warning — a per-send warning would be noise. */
29
29
  private smtpTlsWarningEmitted = false;
30
30
 
31
+ /** Once-per-process latch for a Reply-To that is not a string. */
32
+ private replyToWarningEmitted = false;
33
+
31
34
  /**
32
35
  * Inject services
33
36
  */
@@ -76,6 +79,31 @@ export class EmailService implements OnModuleDestroy {
76
79
  );
77
80
  }
78
81
 
82
+ /**
83
+ * The Reply-To of one mail (11.43.6+): the call's `replyTo`, else `email.defaultReplyTo`, else none. `false` (or an
84
+ * empty string) sends this mail without one. A value that is not a string is dropped with a warning instead of
85
+ * failing the mail: a misconfigured default must not stop password-reset or verification mail.
86
+ */
87
+ protected resolveReplyTo(replyTo: false | string | undefined): string | undefined {
88
+ if (replyTo === false) {
89
+ return undefined;
90
+ }
91
+ const value: unknown = replyTo ?? this.configService.getFastButReadOnly('email.defaultReplyTo');
92
+ if (value === undefined || value === null) {
93
+ return undefined;
94
+ }
95
+ if (typeof value === 'string') {
96
+ return value.trim() || undefined;
97
+ }
98
+ if (!this.replyToWarningEmitted) {
99
+ this.replyToWarningEmitted = true;
100
+ this.emailServiceLogger.warn(
101
+ `Reply-To ignored: expected an address string (email.defaultReplyTo / replyTo), got ${typeof value}.`,
102
+ );
103
+ }
104
+ return undefined;
105
+ }
106
+
79
107
  onModuleDestroy(): void {
80
108
  if (this.cachedTransporter) {
81
109
  this.cachedTransporter.close();
@@ -94,6 +122,8 @@ export class EmailService implements OnModuleDestroy {
94
122
  attachments?: Attachment[];
95
123
  html?: string;
96
124
  htmlTemplate?: string;
125
+ /** Reply-To of this mail; overrides `email.defaultReplyTo`, `false` sends it without one (11.43.6+) */
126
+ replyTo?: false | string;
97
127
  senderEmail?: string;
98
128
  senderName?: string;
99
129
  smtp?: MailTransportOptions;
@@ -189,11 +219,13 @@ export class EmailService implements OnModuleDestroy {
189
219
  this.cachedSmtpConfig = smtpKey;
190
220
  }
191
221
 
192
- // Send mail
222
+ // Send mail; without a Reply-To the header is left out entirely
223
+ const replyTo = this.resolveReplyTo(config.replyTo);
193
224
  return this.cachedTransporter.sendMail({
194
225
  attachments,
195
226
  from: `"${senderName}" <${senderEmail}>`,
196
227
  html,
228
+ ...(replyTo ? { replyTo } : {}),
197
229
  subject,
198
230
  text,
199
231
  to: recipients,