Skip to content

Commit 342ebb0

Browse files
committed
feat: enhance mail sending functionality with subject handling
- Added subject field to mail sending API, ensuring it is required for sending emails. - Updated MailSendService and MailSendController to incorporate subject validation and usage. - Modified mail template modal to include input for subject, improving user experience. - Adjusted related components to support subject management in email sending processes.
1 parent c057c48 commit 342ebb0

7 files changed

Lines changed: 57 additions & 18 deletions

File tree

apps/api/defaults/mail/mail_templates.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@ mailTemplates:
22
# Déclare les variables disponibles côté UI pour construire le payload `variables`.
33
# Les valeurs peuvent contenir des placeholders Liquid `{{ ... }}` résolus via `resolveConfigVariables`.
44
variables:
5-
- key: subject
6-
label: Sujet du mail
7-
description: Sujet affiché par le client mail.
8-
example: "Bienvenue sur Sesame"
9-
defaultValue: "Notification"
10-
115
- key: appName
126
label: Nom de l'application
137
description: Nom affiché dans certains templates.

apps/api/src/management/mail/_dto/mail-send-many.dto.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ApiProperty } from '@nestjs/swagger';
22
import { Types } from 'mongoose';
3-
import { IsArray, IsIn, IsObject, IsOptional, IsString } from 'class-validator';
3+
import { IsArray, IsIn, IsNotEmpty, IsObject, IsOptional, IsString } from 'class-validator';
44

55
export class MailSendManyDto {
66
@ApiProperty({ description: 'Ids des identities destinataires' })
@@ -11,6 +11,11 @@ export class MailSendManyDto {
1111
@IsString()
1212
public template: string;
1313

14+
@ApiProperty({ description: "Sujet du mail (en-tête SMTP, distinct des variables du template)" })
15+
@IsString()
16+
@IsNotEmpty()
17+
public subject: string;
18+
1419
@ApiProperty({ required: false, description: 'Variables additionnelles injectées dans le template' })
1520
@IsOptional()
1621
@IsObject()

apps/api/src/management/mail/mail-send.controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export class MailSendController {
2323
const result = await this.mailSend.sendTemplateToIdentities({
2424
ids: (body.ids || []).map((id) => String(id)),
2525
template: body.template,
26+
subject: body.subject,
2627
variables: body.variables,
2728
recipientAddressSource: body.recipientAddressSource,
2829
});

apps/api/src/management/mail/mail-send.service.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,23 @@ export class MailSendService {
2020
public async sendTemplateToIdentities(args: {
2121
ids: string[];
2222
template: string;
23+
subject: string;
2324
variables?: Record<string, string>;
2425
recipientAddressSource?: 'principal' | 'personnel';
2526
}): Promise<{ sent: number; skipped: number }> {
2627
const template = String(args.template || '').trim();
2728
if (!template) {
2829
throw new BadRequestException('Template requis');
2930
}
31+
const subject = String(args.subject || '').trim();
32+
if (!subject) {
33+
throw new BadRequestException('Sujet requis');
34+
}
3035
const variables = (args.variables && typeof args.variables === 'object' ? args.variables : {}) as Record<
3136
string,
3237
any
3338
>;
39+
const { subject: _subjectVar, ...templateVariables } = variables;
3440

3541
const smtp = await this.mailadmService.getParams();
3642
const principalPath = String(smtp?.recipientJsonPathEmailPrincipal || '').trim();
@@ -81,11 +87,12 @@ export class MailSendService {
8187
try {
8288
await this.mailer.sendMail({
8389
to,
84-
subject: variables?.subject || 'Notification',
90+
subject,
8591
template,
8692
context: {
8793
identity,
88-
...variables,
94+
subject,
95+
...templateVariables,
8996
},
9097
});
9198
sent++;

apps/web/src/components/pages/identities/modals/mail-template.vue

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ q-dialog(
3636
:loading="templatesLoading"
3737
color="teal-7"
3838
)
39+
q-input.q-mt-md(
40+
v-model="mailSubject"
41+
label="Sujet du mail"
42+
hint="Sujet affiché par le client mail (obligatoire)"
43+
outlined
44+
dense
45+
color="teal-7"
46+
autocomplete="off"
47+
)
3948
q-banner.q-mt-md(
4049
v-if="mailPathsReady && recipientSourceOptions.length === 0"
4150
rounded
@@ -195,6 +204,15 @@ q-dialog(
195204
:loading="templatesLoading"
196205
color="teal-7"
197206
)
207+
q-input.q-mt-md(
208+
v-model="mailSubject"
209+
label="Sujet du mail"
210+
hint="Sujet affiché par le client mail (obligatoire)"
211+
outlined
212+
dense
213+
color="teal-7"
214+
autocomplete="off"
215+
)
198216
q-banner.q-mt-md(
199217
v-if="mailPathsReady && recipientSourceOptions.length === 0"
200218
rounded
@@ -336,6 +354,7 @@ q-dialog(
336354

337355
<script lang="ts" setup>
338356
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
357+
import { watchDebounced } from '@vueuse/core'
339358
import { useDialogPluginComponent, useQuasar } from 'quasar'
340359
341360
const props = defineProps({
@@ -463,6 +482,7 @@ const showSendAll = computed(() => {
463482
464483
const initAllIdentities = ref(false)
465484
const templateName = ref<string>('')
485+
const mailSubject = ref<string>('')
466486
const templates = ref<{ label: string; value: string }[]>([])
467487
const templatesLoading = ref(false)
468488
const previewHtml = ref('')
@@ -489,6 +509,7 @@ const recipientSourceOptions = computed(() => {
489509
const canSendMailTemplate = computed(
490510
() =>
491511
mailPathsReady.value &&
512+
String(mailSubject.value || '').trim().length > 0 &&
492513
(recipientAddressSource.value === 'principal' || recipientAddressSource.value === 'personnel'),
493514
)
494515
@@ -499,6 +520,9 @@ const sendButtonTitle = computed(() => {
499520
if (!mailPathsReady.value) {
500521
return 'Chargement des paramètres SMTP…'
501522
}
523+
if (String(mailSubject.value || '').trim().length === 0) {
524+
return 'Renseignez le sujet du mail.'
525+
}
502526
if (recipientSourceOptions.value.length === 0) {
503527
return 'Configurez au moins un chemin JSON (e-mail personnel ou principal) dans Paramètres → Serveur SMTP.'
504528
}
@@ -517,7 +541,7 @@ const variablesObject = computed(() => {
517541
const out: Record<string, string> = {}
518542
for (const row of variablesRows.value) {
519543
const k = String(row.key || '').trim()
520-
if (!k) continue
544+
if (!k || k === 'subject') continue
521545
out[k] = String(row.value ?? '')
522546
}
523547
return out
@@ -529,7 +553,7 @@ const variablesToSend = computed(() => {
529553
// Toujours envoyer les variables déclarées dans la config (avec leur défaut)
530554
for (const v of availableVariables.value) {
531555
const key = String(v?.key || '').trim()
532-
if (!key) continue
556+
if (!key || key === 'subject') continue
533557
base[key] = v.defaultValue !== undefined && v.defaultValue !== null ? String(v.defaultValue) : ''
534558
}
535559
@@ -582,7 +606,7 @@ async function fetchTemplatesConfig() {
582606
try {
583607
const res = await (useNuxtApp() as any).$http.get('/management/mail/templates/config', { method: 'GET' })
584608
const vars = res?._data?.data?.variables || []
585-
availableVariables.value = Array.isArray(vars) ? vars : []
609+
availableVariables.value = Array.isArray(vars) ? vars.filter((v: { key?: string }) => String(v?.key || '').trim() !== 'subject') : []
586610
} catch {
587611
availableVariables.value = []
588612
}
@@ -597,6 +621,7 @@ async function refreshPreview() {
597621
template: templateName.value,
598622
variables: {
599623
...variablesToSend.value,
624+
subject: String(mailSubject.value || '').trim(),
600625
identity,
601626
},
602627
},
@@ -617,12 +642,13 @@ onMounted(async () => {
617642
await refreshPreview()
618643
})
619644
620-
watch(
621-
() => [templateName.value, variablesToSend.value],
622-
async () => {
623-
await refreshPreview()
645+
// Ne pas watcher variablesToSend (nouvel objet à chaque lecture) : boucle de re-renders + perte de focus/valeur sur q-input.
646+
watchDebounced(
647+
[templateName, mailSubject, variablesRows, availableVariables],
648+
() => {
649+
void refreshPreview()
624650
},
625-
{ deep: true },
651+
{ debounce: 400, deep: true },
626652
)
627653
628654
watch(
@@ -650,6 +676,7 @@ const syncIdentities = () => {
650676
onDialogOK({
651677
initAllIdentities: initAllIdentities.value,
652678
template: templateName.value,
679+
subject: String(mailSubject.value || '').trim(),
653680
variables: variablesToSend.value,
654681
recipientAddressSource: recipientAddressSource.value,
655682
})

apps/web/src/pages/identities/table.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,13 +550,17 @@ export default defineNuxtComponent({
550550
}
551551
},
552552
553-
async sendTemplateMailToIdentities(identities, data: { template?: string; variables?: Record<string, string> }) {
553+
async sendTemplateMailToIdentities(
554+
identities,
555+
data: { template?: string; subject?: string; variables?: Record<string, string>; recipientAddressSource?: string },
556+
) {
554557
const ids = this.bulkIdsFromIdentities(identities)
555558
try {
556559
const result = await this.$http.post('/management/mail/sendmany', {
557560
body: {
558561
ids,
559562
template: data?.template,
563+
subject: data?.subject,
560564
variables: data?.variables,
561565
...(data?.recipientAddressSource ? { recipientAddressSource: data.recipientAddressSource } : {}),
562566
},

apps/web/src/pages/identities/table/[_id]/index.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ export default defineNuxtComponent({
438438
body: {
439439
ids: [this.identity._id],
440440
template: data?.template,
441+
subject: data?.subject,
441442
variables: data?.variables,
442443
...(data?.recipientAddressSource ? { recipientAddressSource: data.recipientAddressSource } : {}),
443444
},

0 commit comments

Comments
 (0)