From 9a9353aee5c3d8eeb0ed5d32a9fc4acfeb4a1665 Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Tue, 3 Mar 2026 21:11:29 +0100 Subject: [PATCH 1/2] Add consistent email footer across all templates Every email now gets a unified footer: a configurable sign-off (defaulting to "Regards, The Thesis Management Team.") followed by a notification settings link appended centrally by MailBuilder.send(). - Add emailSignature field to ResearchGroupSettings for per-group customization - Set default emailSignature template variable in MailBuilder constructor - Append notification settings link centrally in MailBuilder.send() after Thymeleaf rendering - Replace hardcoded "Best regards" sign-offs and broken ${config.signature} references with ${emailSignature} in all templates - Remove per-template notification link blocks (now handled centrally) - Add Liquibase migration 36 for schema + existing template data updates - Add assertEmailFooter e2e helper and footer assertions in 6 test specs Co-Authored-By: Claude Opus 4.6 --- .../e2e/application-review-workflow.spec.ts | 4 + client/e2e/application-workflow.spec.ts | 3 + client/e2e/mailpit.ts | 13 ++ client/e2e/presentation-workflow.spec.ts | 2 + client/e2e/proposal-feedback-workflow.spec.ts | 3 + client/e2e/thesis-grading-workflow.spec.ts | 2 + client/e2e/thesis-workflow.spec.ts | 2 + .../responses/researchGroupSettings.ts | 1 + .../ResearchGroupSettingsController.java | 2 + .../dto/ResearchGroupSettingsEmailDTO.java | 5 +- .../thesis/entity/ResearchGroupSettings.java | 3 + .../aet/thesis/service/MailingService.java | 37 +++++- .../cit/aet/thesis/utility/MailBuilder.java | 21 ++- ...atic_rejection_reminder_email_template.sql | 3 +- ...dd_interview_invitation_email_template.sql | 12 +- .../db/changelog/changes/28_data_export.sql | 2 +- .../changes/36_consistent_email_footer.sql | 44 +++++++ .../db/changelog/db.changelog-master.xml | 1 + .../manual/insert_tum_aet_email_templates.sql | 120 +++++------------- .../insert_tum_default_email_templates.sql | 113 ++++------------- 20 files changed, 201 insertions(+), 192 deletions(-) create mode 100644 server/src/main/resources/db/changelog/changes/36_consistent_email_footer.sql diff --git a/client/e2e/application-review-workflow.spec.ts b/client/e2e/application-review-workflow.spec.ts index cc6659c9c..c0a795768 100644 --- a/client/e2e/application-review-workflow.spec.ts +++ b/client/e2e/application-review-workflow.spec.ts @@ -6,6 +6,7 @@ import { getBody, getToAddresses, assertSentFromApp, + assertEmailFooter, findBySubject, } from './mailpit' @@ -63,6 +64,7 @@ test.describe('Application Review Workflow', () => { const rejectionEmail = findBySubject(newEmails, 'Thesis Application Rejection') expect(rejectionEmail, 'Rejection email with correct subject should be sent').toBeDefined() assertSentFromApp(rejectionEmail!) + assertEmailFooter(rejectionEmail!) expect(getToAddresses(rejectionEmail!)).toContain('student4@test.local') // Body should greet the student by first name and reference the topic title @@ -133,6 +135,7 @@ test.describe('Application Review Workflow', () => { const acceptanceEmail = findBySubject(newEmails, 'Thesis Application Acceptance') expect(acceptanceEmail, 'Acceptance email should be sent').toBeDefined() assertSentFromApp(acceptanceEmail!) + assertEmailFooter(acceptanceEmail!) expect(getToAddresses(acceptanceEmail!)).toContain('student5@test.local') // Body should greet student, mention the supervisor, and include a thesis link @@ -145,6 +148,7 @@ test.describe('Application Review Workflow', () => { const thesisEmail = findBySubject(newEmails, 'Thesis Created') expect(thesisEmail, 'Thesis creation email should be sent').toBeDefined() assertSentFromApp(thesisEmail!) + assertEmailFooter(thesisEmail!) expect(getToAddresses(thesisEmail!)).toContain('student5@test.local') // Body should contain the thesis title and a link to the thesis diff --git a/client/e2e/application-workflow.spec.ts b/client/e2e/application-workflow.spec.ts index 5577987f4..7655053c8 100644 --- a/client/e2e/application-workflow.spec.ts +++ b/client/e2e/application-workflow.spec.ts @@ -12,6 +12,7 @@ import { getBody, getToAddresses, assertSentFromApp, + assertEmailFooter, findBySubject, } from './mailpit' @@ -121,6 +122,7 @@ test.describe('Application Workflow - Student submits application', () => { const studentEmail = findBySubject(newStudentEmails, 'Thesis Application Confirmation') expect(studentEmail, 'Student confirmation email should be sent').toBeDefined() assertSentFromApp(studentEmail!) + assertEmailFooter(studentEmail!) expect(getToAddresses(studentEmail!)).toContain('student@test.local') const studentBody = getBody(studentEmail!) @@ -141,6 +143,7 @@ test.describe('Application Workflow - Student submits application', () => { 'Examiner (examiner@test.local) should receive "New Thesis Application" email', ).toBeDefined() assertSentFromApp(examinerChairEmail!) + assertEmailFooter(examinerChairEmail!) const examinerBody = getBody(examinerChairEmail!) expect(examinerBody, 'Examiner email should mention the applicant').toContain('Student') diff --git a/client/e2e/mailpit.ts b/client/e2e/mailpit.ts index 4e7178124..036d00496 100644 --- a/client/e2e/mailpit.ts +++ b/client/e2e/mailpit.ts @@ -137,6 +137,19 @@ export function assertBodyContains(message: MailpitMessage, expectedText: string expect(body, `Expected email body to contain "${expectedText}"`).toContain(expectedText) } +/** + * Assert that the email contains the standard footer with notification settings link. + */ +export function assertEmailFooter(message: MailpitMessage): void { + const body = getBody(message) + expect(body, 'Expected email to contain notification settings link').toContain( + 'notification settings in Thesis Management', + ) + expect(body, 'Expected email to contain /settings/notifications link').toContain( + '/settings/notifications', + ) +} + /** * Assert that the email was sent from the application sender. * Verifies the From header matches the expected format: "ThesisManagement <...@...>" diff --git a/client/e2e/presentation-workflow.spec.ts b/client/e2e/presentation-workflow.spec.ts index e4c45eada..022551433 100644 --- a/client/e2e/presentation-workflow.spec.ts +++ b/client/e2e/presentation-workflow.spec.ts @@ -7,6 +7,7 @@ import { getBody, getToAddresses, assertSentFromApp, + assertEmailFooter, } from './mailpit' // Thesis d000-0003 is in SUBMITTED state, assigned to student3, has abstract text set @@ -166,6 +167,7 @@ test.describe.serial('Presentation Workflow', () => { const privateEmail = newEmails.find((e) => getSubject(e) === 'New Presentation scheduled') expect(privateEmail, 'Presentation scheduled email should be sent').toBeDefined() assertSentFromApp(privateEmail!) + assertEmailFooter(privateEmail!) expect(getToAddresses(privateEmail!)).toContain('student3@test.local') // Body should contain: greeting, thesis title, presentation location, diff --git a/client/e2e/proposal-feedback-workflow.spec.ts b/client/e2e/proposal-feedback-workflow.spec.ts index 54b73007b..be748bdd8 100644 --- a/client/e2e/proposal-feedback-workflow.spec.ts +++ b/client/e2e/proposal-feedback-workflow.spec.ts @@ -7,6 +7,7 @@ import { getBody, getToAddresses, assertSentFromApp, + assertEmailFooter, hasAttachment, } from './mailpit' @@ -59,6 +60,7 @@ test.describe('Proposal Upload - Student uploads proposal', () => { const proposalEmail = newEmails.find((e) => getSubject(e) === 'Thesis Proposal Added') expect(proposalEmail, 'Proposal upload email with correct subject should be sent').toBeDefined() assertSentFromApp(proposalEmail!) + assertEmailFooter(proposalEmail!) expect(getToAddresses(proposalEmail!)).toContain('supervisor@test.local') // Body should reference the thesis title, the uploader name, and include a link @@ -128,6 +130,7 @@ test.describe('Proposal Feedback - Supervisor requests changes', () => { 'Change request email with correct subject should be sent', ).toBeDefined() assertSentFromApp(changeRequestEmail!) + assertEmailFooter(changeRequestEmail!) expect(getToAddresses(changeRequestEmail!)).toContain('student2@test.local') // Body should greet the student, reference the thesis title, mention the reviewer, diff --git a/client/e2e/thesis-grading-workflow.spec.ts b/client/e2e/thesis-grading-workflow.spec.ts index 1e711a30b..1338a8ebe 100644 --- a/client/e2e/thesis-grading-workflow.spec.ts +++ b/client/e2e/thesis-grading-workflow.spec.ts @@ -7,6 +7,7 @@ import { getBody, getToAddresses, assertSentFromApp, + assertEmailFooter, } from './mailpit' // Thesis d000-0003: SUBMITTED state, student3, supervisor2, examiner2 (DSA group) @@ -174,6 +175,7 @@ test.describe.serial('Thesis Grading Workflow', () => { const gradeEmail = newEmails.find((e) => getSubject(e) === 'Final Grade available for Thesis') expect(gradeEmail, 'Final grade email should be sent').toBeDefined() assertSentFromApp(gradeEmail!) + assertEmailFooter(gradeEmail!) expect(getToAddresses(gradeEmail!)).toContain('student3@test.local') // Body should contain: greeting, thesis title, examiner name, final grade, diff --git a/client/e2e/thesis-workflow.spec.ts b/client/e2e/thesis-workflow.spec.ts index 8327ce55f..7d63fbdbe 100644 --- a/client/e2e/thesis-workflow.spec.ts +++ b/client/e2e/thesis-workflow.spec.ts @@ -7,6 +7,7 @@ import { getBody, getToAddresses, assertSentFromApp, + assertEmailFooter, } from './mailpit' test.describe('Thesis Workflow - Examiner creates a thesis', () => { @@ -66,6 +67,7 @@ test.describe('Thesis Workflow - Examiner creates a thesis', () => { const creationEmail = newEmails.find((e) => getSubject(e) === 'Thesis Created') expect(creationEmail, 'Thesis creation email should be sent').toBeDefined() assertSentFromApp(creationEmail!) + assertEmailFooter(creationEmail!) expect(getToAddresses(creationEmail!)).toContain('student4@test.local') // Body should contain: greeting, thesis title, examiner/supervisor/student names, and link diff --git a/client/src/requests/responses/researchGroupSettings.ts b/client/src/requests/responses/researchGroupSettings.ts index 4a0fd1b82..8be075a70 100644 --- a/client/src/requests/responses/researchGroupSettings.ts +++ b/client/src/requests/responses/researchGroupSettings.ts @@ -22,6 +22,7 @@ export interface IResearchGroupSettingsPhase { export interface IResearchGroupSettingsEmail { applicationNotificationEmail?: string | null + emailSignature?: string | null } export interface IResearchGroupSettingsWritingGuide { diff --git a/server/src/main/java/de/tum/cit/aet/thesis/controller/ResearchGroupSettingsController.java b/server/src/main/java/de/tum/cit/aet/thesis/controller/ResearchGroupSettingsController.java index ed7fba1d9..d2a6b42ef 100644 --- a/server/src/main/java/de/tum/cit/aet/thesis/controller/ResearchGroupSettingsController.java +++ b/server/src/main/java/de/tum/cit/aet/thesis/controller/ResearchGroupSettingsController.java @@ -80,6 +80,8 @@ public ResponseEntity createOrUpdateRejectSettings(@Pa String validatedEmail = RequestValidator.validateEmailAllowNull( newSettings.emailSettings().applicationNotificationEmail() == null ? null : newSettings.emailSettings().applicationNotificationEmail().trim()); toSave.setApplicationNotificationEmail(validatedEmail); + String signature = newSettings.emailSettings().emailSignature(); + toSave.setEmailSignature(signature != null && !signature.trim().isEmpty() ? signature.trim() : null); } if (newSettings.writingGuideSettings() != null) { String link = newSettings.writingGuideSettings().scientificWritingGuideLink(); diff --git a/server/src/main/java/de/tum/cit/aet/thesis/dto/ResearchGroupSettingsEmailDTO.java b/server/src/main/java/de/tum/cit/aet/thesis/dto/ResearchGroupSettingsEmailDTO.java index 525b99f5f..01024a214 100644 --- a/server/src/main/java/de/tum/cit/aet/thesis/dto/ResearchGroupSettingsEmailDTO.java +++ b/server/src/main/java/de/tum/cit/aet/thesis/dto/ResearchGroupSettingsEmailDTO.java @@ -3,9 +3,10 @@ import de.tum.cit.aet.thesis.entity.ResearchGroupSettings; public record ResearchGroupSettingsEmailDTO( - String applicationNotificationEmail + String applicationNotificationEmail, + String emailSignature ) { public static ResearchGroupSettingsEmailDTO fromEntity(ResearchGroupSettings settings) { - return new ResearchGroupSettingsEmailDTO(settings.getApplicationNotificationEmail()); + return new ResearchGroupSettingsEmailDTO(settings.getApplicationNotificationEmail(), settings.getEmailSignature()); } } diff --git a/server/src/main/java/de/tum/cit/aet/thesis/entity/ResearchGroupSettings.java b/server/src/main/java/de/tum/cit/aet/thesis/entity/ResearchGroupSettings.java index 5d4f3074f..496fa9749 100644 --- a/server/src/main/java/de/tum/cit/aet/thesis/entity/ResearchGroupSettings.java +++ b/server/src/main/java/de/tum/cit/aet/thesis/entity/ResearchGroupSettings.java @@ -39,4 +39,7 @@ public class ResearchGroupSettings { @Column(name = "include_application_data_in_email", nullable = false) private boolean includeApplicationDataInEmail = false; + + @Column(name = "email_signature") + private String emailSignature; } diff --git a/server/src/main/java/de/tum/cit/aet/thesis/service/MailingService.java b/server/src/main/java/de/tum/cit/aet/thesis/service/MailingService.java index 56edae860..88bc13eb3 100644 --- a/server/src/main/java/de/tum/cit/aet/thesis/service/MailingService.java +++ b/server/src/main/java/de/tum/cit/aet/thesis/service/MailingService.java @@ -107,6 +107,7 @@ public void sendApplicationCreatedEmail(Application application) { "en"); MailBuilder studentMailBuilder = new MailBuilder(config, studentEmailTemplate.getSubject(), studentEmailTemplate.getBodyHtml()); + applyGroupSignature(studentMailBuilder, application.getResearchGroup()); studentMailBuilder .addPrimaryRecipient(application.getUser()) .addStoredAttachment(application.getUser().getCvFilename(), @@ -123,7 +124,7 @@ public void sendApplicationCreatedEmail(Application application) { } private MailBuilder prepareApplicationCreatedMailBuilder(Application application, EmailTemplate template) { - return new MailBuilder(config, template.getSubject(), template.getBodyHtml()) + MailBuilder builder = new MailBuilder(config, template.getSubject(), template.getBodyHtml()) .addStoredAttachment(application.getUser().getCvFilename(), getUserFilename(application.getUser(), "CV", application.getUser().getCvFilename())) @@ -134,6 +135,8 @@ private MailBuilder prepareApplicationCreatedMailBuilder(Application application getUserFilename(application.getUser(), "Degree Report", application.getUser().getDegreeFilename())) .fillApplicationPlaceholders(application); + applyGroupSignature(builder, application.getResearchGroup()); + return builder; } private MailBuilder prepareMinimalApplicationMailBuilder(Application application) { @@ -167,7 +170,9 @@ private MailBuilder prepareMinimalApplicationMailBuilder(Application application + "" + applicationUrl + "

"; - return new MailBuilder(config, subject, body); + MailBuilder builder = new MailBuilder(config, subject, body); + applyGroupSignature(builder, application.getResearchGroup()); + return builder; } /** @@ -228,6 +233,7 @@ public void sendApplicationAcceptanceEmail(Application application, Thesis thesi templateCase, "en"); MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml()); + applyGroupSignature(mailBuilder, application.getResearchGroup()); mailBuilder .addPrimaryRecipient(application.getUser()) .addSecondaryRecipient(advisor) @@ -250,6 +256,7 @@ public void sendApplicationRejectionEmail(Application application, ApplicationRe reason.getTemplateCase(), "en"); MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml()); + applyGroupSignature(mailBuilder, application.getResearchGroup()); mailBuilder .addPrimaryRecipient(application.getUser()) .addDefaultBccRecipients(application.getResearchGroup().getHead().getEmail()) @@ -269,6 +276,7 @@ public void sendApplicationReminderEmail(User user, long unreviewedApplications) "APPLICATION_REMINDER", "en"); MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml()); + applyGroupSignature(mailBuilder, user.getResearchGroup()); mailBuilder .addPrimaryRecipient(user) .addNotificationName("unreviewed-application-reminder") @@ -294,6 +302,7 @@ public void sendApplicationAutomaticRejectReminderEmail(User user, List(); this.variables.put("config", config.getConfigDto()); + this.variables.put("emailSignature", "

Regards,
The Thesis Management Team.

"); this.fileAttachments = new ArrayList<>(); this.rawAttachments = new ArrayList<>(); @@ -369,6 +370,19 @@ public MailBuilder fillPlaceholder(String placeholder, Object value) { return this; } + /** + * Overrides the default email signature with a custom one. + * + * @param signature the custom HTML signature + * @return this builder + */ + public MailBuilder withSignature(String signature) { + if (signature != null && !signature.isBlank()) { + this.variables.put("emailSignature", signature); + } + return this; + } + /** * Fills user placeholders under the given placeholder key. * @@ -573,7 +587,12 @@ public void send(JavaMailSender mailSender, UploadService uploadService) { Multipart messageContent = new MimeMultipart(); BodyPart messageBody = new MimeBodyPart(); - messageBody.setContent(config.getTemplateEngine().process(templateHtml, templateContext), "text/html; charset=utf-8"); + String renderedHtml = config.getTemplateEngine().process(templateHtml, templateContext); + renderedHtml += "
" + + "You can (un)subscribe to similar emails in your " + + "" + + "notification settings in Thesis Management.
"; + messageBody.setContent(renderedHtml, "text/html; charset=utf-8"); messageContent.addBodyPart(messageBody); for (StoredAttachment data : fileAttachments) { diff --git a/server/src/main/resources/db/changelog/changes/14_add_automatic_rejection_reminder_email_template.sql b/server/src/main/resources/db/changelog/changes/14_add_automatic_rejection_reminder_email_template.sql index 1e8d18f7f..86485a5df 100644 --- a/server/src/main/resources/db/changelog/changes/14_add_automatic_rejection_reminder_email_template.sql +++ b/server/src/main/resources/db/changelog/changes/14_add_automatic_rejection_reminder_email_template.sql @@ -21,7 +21,6 @@ The following application(s) require your attention:

Please review the applications, if you don''t want them to be rejected automatically.

-

Best regards,
-The Thesis Coordination Team

', +
', 'This will be send out when you have soon to be automatically rejected applications.' ); \ No newline at end of file diff --git a/server/src/main/resources/db/changelog/changes/19_add_interview_invitation_email_template.sql b/server/src/main/resources/db/changelog/changes/19_add_interview_invitation_email_template.sql index 9a74ddd58..71db6c19a 100644 --- a/server/src/main/resources/db/changelog/changes/19_add_interview_invitation_email_template.sql +++ b/server/src/main/resources/db/changelog/changes/19_add_interview_invitation_email_template.sql @@ -23,8 +23,7 @@ VALUES Schedule your interview

-

Best regards,
-The Thesis Coordination Team

', +
', 'Invitation email sent to interviewees with scheduling link, applicant name and thesis title' ); @@ -53,8 +52,7 @@ VALUES If you already scheduled your interview, please ignore this reminder.

-

Best regards,
-The Thesis Coordination Team

', +
', 'Reminder email sent when resending the interview scheduling invitation' ); @@ -102,8 +100,7 @@ VALUES View or change your booking

-

Best regards,
-The Thesis Coordination Team

', +
', 'Confirmation email sent to interviewees when a slot was booked' ); @@ -133,7 +130,6 @@ You can book a new slot using the link below: Book a new Slot

-

Best regards,
-The Thesis Coordination Team

', +
', 'Confirmation email sent to interviewees when a slot was canceled' ); \ No newline at end of file diff --git a/server/src/main/resources/db/changelog/changes/28_data_export.sql b/server/src/main/resources/db/changelog/changes/28_data_export.sql index 8831b959a..2879162b4 100644 --- a/server/src/main/resources/db/changelog/changes/28_data_export.sql +++ b/server/src/main/resources/db/changelog/changes/28_data_export.sql @@ -28,5 +28,5 @@ Your personal data export has been generated and is ready for download. You can Please note that the download link will expire in 7 days. After that, you can request a new export.

-

', 'en', 'Notification when data export is ready for download', NOW(), NULL, NOW()) +
', 'en', 'Notification when data export is ready for download', NOW(), NULL, NOW()) ON CONFLICT (template_case, language) WHERE research_group_id IS NULL DO NOTHING; diff --git a/server/src/main/resources/db/changelog/changes/36_consistent_email_footer.sql b/server/src/main/resources/db/changelog/changes/36_consistent_email_footer.sql new file mode 100644 index 000000000..25e63065a --- /dev/null +++ b/server/src/main/resources/db/changelog/changes/36_consistent_email_footer.sql @@ -0,0 +1,44 @@ +--liquibase formatted sql +--changeset consistent-email-footer:36-add-email-signature-column +ALTER TABLE research_group_settings ADD COLUMN email_signature TEXT; + +--changeset consistent-email-footer:36-replace-best-regards-with-signature-variable +-- Replace hardcoded "Best regards" sign-offs with the emailSignature template variable +UPDATE email_templates SET body_html = REPLACE(body_html, + '

Best regards,
' || chr(10) || 'The Thesis Coordination Team

', + '
') +WHERE body_html LIKE '%Best regards%Thesis Coordination Team%'; + +--changeset consistent-email-footer:36-replace-config-signature-with-email-signature +-- Replace AET ${config.signature} with ${emailSignature} +UPDATE email_templates SET body_html = REPLACE(body_html, + '
', + '
') +WHERE body_html LIKE '%config.signature%'; + +--changeset consistent-email-footer:36-remove-notification-links-multiline +-- Remove multiline notification settings link blocks +UPDATE email_templates SET body_html = REPLACE(body_html, + '
' || chr(10) || '
' || chr(10) || ' Manage your notification settings here' || chr(10) || '
' || chr(10) || '

', + '') +WHERE body_html LIKE '%Manage your notification settings%'; + +--changeset consistent-email-footer:36-remove-notification-links-inline +-- Remove inline notification settings link blocks (presentation invitation templates) +UPDATE email_templates SET body_html = REPLACE(body_html, + '


' || chr(10) || '
Manage your notification settings here
', + '') +WHERE body_html LIKE '%Manage your notification settings%'; + +--changeset consistent-email-footer:36-remove-trailing-br +-- Remove trailing

from DATA_EXPORT_READY template +UPDATE email_templates SET body_html = REPLACE(body_html, + '

', + '') +WHERE template_case = 'DATA_EXPORT_READY' AND body_html LIKE '%

%'; + +--changeset consistent-email-footer:36-add-signature-to-templates-without +-- Add emailSignature variable to templates that don't have any sign-off +UPDATE email_templates SET body_html = body_html || chr(10) || '
' +WHERE body_html NOT LIKE '%emailSignature%' + AND body_html NOT LIKE '%Best regards%'; diff --git a/server/src/main/resources/db/changelog/db.changelog-master.xml b/server/src/main/resources/db/changelog/db.changelog-master.xml index ccdfa343b..08f08b15c 100644 --- a/server/src/main/resources/db/changelog/db.changelog-master.xml +++ b/server/src/main/resources/db/changelog/db.changelog-master.xml @@ -38,6 +38,7 @@ + diff --git a/server/src/main/resources/db/changelog/manual/insert_tum_aet_email_templates.sql b/server/src/main/resources/db/changelog/manual/insert_tum_aet_email_templates.sql index 580ff3279..42bbde16c 100644 --- a/server/src/main/resources/db/changelog/manual/insert_tum_aet_email_templates.sql +++ b/server/src/main/resources/db/changelog/manual/insert_tum_aet_email_templates.sql @@ -64,7 +64,7 @@ WITH defaults You can view your thesis details and tasks on: [[${thesisUrl}]]

-
+
', 'Application was accepted with different supervisor and examiner'), ('APPLICATION_ACCEPTED_NO_SUPERVISOR', 'Thesis Application Acceptance', '

Dear [[${recipient.firstName}]],

@@ -93,7 +93,7 @@ project. You can view your thesis details and tasks on: [[${thesisUrl}]]

-
', 'Application was accepted with same supervisor and examiner'), +
', 'Application was accepted with same supervisor and examiner'), ('APPLICATION_CREATED_CHAIR', 'New Thesis Application', '

Dear [[${recipient.firstName}]],

there is a new thesis application by [[${application.applicantFirstName}]] [[${application.applicantLastName}]].

@@ -158,11 +158,7 @@ Full Details: You can find the submitted files in the attachment part of this email.

-
-
-

', +
', 'All examiners and supervisors get a summary about a new application'), ('APPLICATION_CREATED_STUDENT', 'Thesis Application Confirmation', '

Dear [[${recipient.firstName}]],

@@ -229,7 +225,9 @@ and research commitments of our group may result in a response time of up to fou We appreciate your patience and understanding during this period.

-

You can find the submitted files in the attachment part of this email.

', +

You can find the submitted files in the attachment part of this email.

+ +
', 'Confirmation email to the applying student when application was submitted'), ('APPLICATION_REJECTED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

@@ -244,7 +242,7 @@ The volume of applications received this year was exceptionally high, and I have I recommend reaching out to other faculty members who may align more closely with your qualifications and area of interest.

-
', 'Application was rejected'), +
', 'Application was rejected'), ('APPLICATION_REJECTED_TOPIC_REQUIREMENTS', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

@@ -256,7 +254,7 @@ After reviewing your application and supporting documents, I regret to inform yo I recommend considering other thesis topics or reaching out to faculty members whose research focus may better align with your qualifications.

-
', 'Application was rejected because topic requirements were not met'), +
', 'Application was rejected because topic requirements were not met'), ('APPLICATION_REJECTED_STUDENT_REQUIREMENTS', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

@@ -269,7 +267,7 @@ Unfortunately, I must inform you that you do not currently meet the necessary re I recommend reaching out to other faculty members who may align more closely with your qualifications and area of interest.

-
', +
', 'Application was rejected because student does not fulfil chair''s requirements'), ('APPLICATION_REJECTED_TITLE_NOT_INTERESTING', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

@@ -283,7 +281,7 @@ However, the suggested topic does not align with the current research interests I encourage you to explore other faculty members whose research is more closely related to your proposed area of study.

-
', +
', 'Application was rejected because the suggested thesis title is not interesting'), ('APPLICATION_REJECTED_TOPIC_FILLED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

@@ -300,7 +298,7 @@ We found a student for the specific topic you applied for. You can explore other topics or suggest a topic yourself in your area of interest.

-
', 'Application was rejected because topic was closed'), +
', 'Application was rejected because topic was closed'), ('APPLICATION_REJECTED_TOPIC_OUTDATED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

@@ -316,7 +314,7 @@ I wanted to inform you that the topic you applied for is no longer available. You can explore other topics or suggest a topic yourself in your area of interest.

-
', 'Application was rejected because topic is outdated'), +
', 'Application was rejected because topic is outdated'), ('APPLICATION_REMINDER', 'Unreviewed Thesis Applications', '

Dear [[${recipient.firstName}]],

@@ -327,11 +325,7 @@ There are currently [[${unreviewedApplications}]] unreviewed th Review Applications: [[${reviewApplicationsLink}]]

-
-
- Manage your notification settings here -
-

', +
', 'Weekly email if there are more than 10 unreviewed applications'), ('THESIS_ASSESSMENT_ADDED', 'Assessment added', '

Dear [[${recipient.firstName}]],

@@ -362,11 +356,7 @@ Review Applications:

-
-
- Manage your notification settings here -
-

', 'Assessment was added to a submitted thesis'), +
', 'Assessment was added to a submitted thesis'), ('THESIS_CLOSED', 'Thesis Closed', '

Dear [[${recipient.firstName}]],

@@ -378,11 +368,7 @@ Please contact your supervisor or examiner if you think that this was a mistake. Full Details:

-
-
- Manage your notification settings here -
-

', 'Thesis was closed before completion'), +
', 'Thesis was closed before completion'), ('THESIS_COMMENT_POSTED', 'A Comment was posted', '

Dear [[${recipient.firstName}]],

@@ -398,11 +384,7 @@ Please contact your supervisor or examiner if you think that this was a mistake. Full Details:

-
-
- Manage your notification settings here -
-

', +
', 'New comment on a thesis. TO depends whether it''s a student or supervisor comment'), ('THESIS_CREATED', 'Thesis Created', '

Dear [[${recipient.firstName}]],

@@ -419,7 +401,9 @@ Please contact your supervisor or examiner if you think that this was a mistake.

The next step is that you write a proposal and submit it on [[${thesisUrl}]] -

', 'New thesis was created and assigned to a student'), +

+ +
', 'New thesis was created and assigned to a student'), ('THESIS_FINAL_GRADE', 'Final Grade available for Thesis', '

Dear [[${recipient.firstName}]],

@@ -439,11 +423,7 @@ The next step is that you write a proposal and submit it on

-
-
- Manage your notification settings here -
-

', 'Final grade was added to a thesis'), +
', 'Final grade was added to a thesis'), ('THESIS_FINAL_SUBMISSION', 'Thesis Submitted', '

Dear [[${recipient.firstName}]],

@@ -458,11 +438,7 @@ The next step is to write an assessment about the thesis. Full Details:

-
-
- Manage your notification settings here -
-

', 'Student submitted final thesis'), +
', 'Student submitted final thesis'), ('THESIS_PRESENTATION_DELETED', 'Presentation deleted', '

Dear [[${recipient.firstName}]],

@@ -473,11 +449,7 @@ The next step is to write an assessment about the thesis. Full Details:

-
-
- Manage your notification settings here -
-

', 'Scheduled presentation was deleted'), +
', 'Scheduled presentation was deleted'), ('THESIS_PRESENTATION_SCHEDULED', 'New Presentation scheduled', '

Dear [[${recipient.firstName}]],

@@ -513,11 +485,7 @@ The next step is to write an assessment about the thesis. Full Details:

-
-
- Manage your notification settings here -
-

', 'New presentation was scheduled'), +
', 'New presentation was scheduled'), ('THESIS_PRESENTATION_UPDATED', 'Presentation updated', '

Dear [[${recipient.firstName}]],

@@ -553,11 +521,7 @@ The next step is to write an assessment about the thesis. Full Details:

-
-
- Manage your notification settings here -
-

', 'Presentation was updated'), +
', 'Presentation was updated'), ('THESIS_PRESENTATION_INVITATION', 'Thesis Presentation Invitation', '

INVITATION

As part of their [[${thesis.type}]]''s thesis
@@ -591,11 +555,7 @@ The presentation will be in [[${presentation.language}]]. Everybody is cordially Full Details: [[${presentationUrl}]]

-
-
- Manage your notification settings here -
-

', 'Public Presentation Invitation'), +
', 'Public Presentation Invitation'), ('THESIS_PRESENTATION_INVITATION_CANCELLED', 'Thesis Presentation Cancelled', '

Dear [[${recipient.firstName}]],

@@ -604,11 +564,7 @@ The [[${thesis.type}]] thesis presentation of [[${presentation.scheduledAt}]] was cancelled.

-
-
- Manage your notification settings here -
-

', 'Public Presentation was deleted'), +
', 'Public Presentation was deleted'), ('THESIS_PRESENTATION_INVITATION_UPDATED', 'Thesis Presentation Updated', '

INVITATION

As part of their [[${thesis.type}]]''s thesis
@@ -642,11 +598,7 @@ The presentation will be in [[${presentation.language}]]. Everybody is cordially Full Details: [[${presentationUrl}]]

-
-
- Manage your notification settings here -
-

', 'Public Presentation was updated'), +
', 'Public Presentation was updated'), ('THESIS_PROPOSAL_ACCEPTED', 'Thesis Proposal Accepted', '

Dear [[${recipient.firstName}]],

@@ -655,11 +607,7 @@ The next step is to start with the project work and with writing the thesis. You can see your submission deadline on [[${thesisUrl}]].

-
-
- Manage your notification settings here -
-

', 'Proposal was accepted'), +
', 'Proposal was accepted'), ('THESIS_PROPOSAL_REJECTED', 'Changes were requested for Proposal', '

Dear [[${recipient.firstName}]],

@@ -677,11 +625,7 @@ The following changes were requested:
Full Details:

-
-
- Manage your notification settings here -
-

', 'Changes were requested for proposal'), +
', 'Changes were requested for proposal'), ('THESIS_PROPOSAL_UPLOADED', 'Thesis Proposal Added', '

Dear [[${recipient.firstName}]],

@@ -693,11 +637,7 @@ You can find the submitted file in the attachment part of this email. Full Details:

-
-
- Manage your notification settings here -
-

', +
', 'Student uploaded new proposal')) AS v(template_case, subject, body_html, description)) INSERT INTO email_templates (email_template_id, diff --git a/server/src/main/resources/db/changelog/manual/insert_tum_default_email_templates.sql b/server/src/main/resources/db/changelog/manual/insert_tum_default_email_templates.sql index 08b5a824f..923ab3978 100644 --- a/server/src/main/resources/db/changelog/manual/insert_tum_default_email_templates.sql +++ b/server/src/main/resources/db/changelog/manual/insert_tum_default_email_templates.sql @@ -49,8 +49,7 @@ FROM defaults d You can view your thesis details and tasks on: [[${thesisUrl}]]

-

Best regards,
-The Thesis Coordination Team

+
', 'Application was accepted with different supervisor and examiner'), ('APPLICATION_ACCEPTED_NO_SUPERVISOR', 'Thesis Application Acceptance', '

Dear [[${recipient.firstName}]],

@@ -77,8 +76,7 @@ thesis. You can view your thesis details and tasks on: [[${thesisUrl}]]

-

Best regards,
-The Thesis Coordination Team

', 'Application was accepted with same supervisor and examiner'), ('APPLICATION_CREATED_CHAIR', 'New Thesis Application', '

Dear [[${recipient.firstName}]],

+
', 'Application was accepted with same supervisor and examiner'), ('APPLICATION_CREATED_CHAIR', 'New Thesis Application', '

Dear [[${recipient.firstName}]],

there is a new thesis application by [[${application.applicantFirstName}]] [[${application.applicantLastName}]].

We received the following thesis application details:

@@ -142,11 +140,7 @@ Full Details: You can find the submitted files in the attachment part of this email.

-
-
-

', 'All examiners and supervisors get a summary about a new application'), ('APPLICATION_CREATED_STUDENT', 'Thesis Application Confirmation', '

Dear [[${recipient.firstName}]],

+
', 'All examiners and supervisors get a summary about a new application'), ('APPLICATION_CREATED_STUDENT', 'Thesis Application Confirmation', '

Dear [[${recipient.firstName}]],

With this email, we confirm your thesis application.

We received the following details:

@@ -211,7 +205,9 @@ and research commitments of our group may result in a response time of up to fou We appreciate your patience and understanding during this period.

-

You can find the submitted files in the attachment part of this email.

', 'Confirmation email to the applying student when application was submitted'), ('APPLICATION_REJECTED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

+

You can find the submitted files in the attachment part of this email.

+ +
', 'Confirmation email to the applying student when application was submitted'), ('APPLICATION_REJECTED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

Thank you for your interest in pursuing your thesis under my supervision. @@ -224,8 +220,7 @@ The volume of applications received this year was exceptionally high, and I have I recommend exploring other supervisors or research groups who may align more closely with your qualifications and area of interest.

-

Best regards,
-The Thesis Coordination Team

', 'Application was rejected'), ('APPLICATION_REJECTED_TOPIC_REQUIREMENTS', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

+
', 'Application was rejected'), ('APPLICATION_REJECTED_TOPIC_REQUIREMENTS', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

Thank you for your interest in pursuing your thesis under my supervision. @@ -236,8 +231,7 @@ After reviewing your application and supporting documents, I regret to inform yo I recommend considering other thesis topics or reaching out to other research groups whose research focus may better align with your qualifications.

-

Best regards,
-The Thesis Coordination Team

', 'Application was rejected because topic requirements were not met'), ('APPLICATION_REJECTED_STUDENT_REQUIREMENTS', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

+
', 'Application was rejected because topic requirements were not met'), ('APPLICATION_REJECTED_STUDENT_REQUIREMENTS', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

Thank you for your interest in pursuing your thesis under my supervision. @@ -249,8 +243,7 @@ Unfortunately, I must inform you that you do not currently meet the necessary re I recommend exploring other supervisors or research groups who may align more closely with your qualifications and area of interest.

-

Best regards,
-The Thesis Coordination Team

', 'Application was rejected because student does not fulfil chair''s requirements'), ('APPLICATION_REJECTED_TITLE_NOT_INTERESTING', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

+
', 'Application was rejected because student does not fulfil chair''s requirements'), ('APPLICATION_REJECTED_TITLE_NOT_INTERESTING', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

Thank you for your interest in pursuing your thesis under my supervision. @@ -262,8 +255,7 @@ However, the suggested topic does not align with the current research interests I encourage you to explore other research groups whose research is more closely related to your proposed area of study.

-

Best regards,
-The Thesis Coordination Team

', 'Application was rejected because the suggested thesis title is not interesting'), ('APPLICATION_REJECTED_TOPIC_FILLED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

+
', 'Application was rejected because the suggested thesis title is not interesting'), ('APPLICATION_REJECTED_TOPIC_FILLED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

Thank you for your interest in pursuing your thesis under my supervision. @@ -278,8 +270,7 @@ We found a student for the specific topic you applied for. You can explore other topics or suggest a topic yourself in your area of interest.

-

Best regards,
-The Thesis Coordination Team

', 'Application was rejected because topic was closed'), ('APPLICATION_REJECTED_TOPIC_OUTDATED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

+
', 'Application was rejected because topic was closed'), ('APPLICATION_REJECTED_TOPIC_OUTDATED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

Thank you for your interest in pursuing your thesis under my supervision. @@ -294,8 +285,7 @@ I wanted to inform you that the topic you applied for is no longer available. You can explore other topics or suggest a topic yourself in your area of interest.

-

Best regards,
-The Thesis Coordination Team

', 'Application was rejected because topic is outdated'), ('APPLICATION_REMINDER', 'Unreviewed Thesis Applications', '

Dear [[${recipient.firstName}]],

+
', 'Application was rejected because topic is outdated'), ('APPLICATION_REMINDER', 'Unreviewed Thesis Applications', '

Dear [[${recipient.firstName}]],

There are currently [[${unreviewedApplications}]] unreviewed thesis applications. @@ -305,11 +295,7 @@ There are currently [[${unreviewedApplications}]] unreviewed th Review Applications: [[${reviewApplicationsLink}]]

-
-
- Manage your notification settings here -
-

', 'Weekly email if there are more than 10 unreviewed applications'), ('THESIS_ASSESSMENT_ADDED', 'Assessment added', '

Dear [[${recipient.firstName}]],

+
', 'Weekly email if there are more than 10 unreviewed applications'), ('THESIS_ASSESSMENT_ADDED', 'Assessment added', '

Dear [[${recipient.firstName}]],

[[${assessment.creatorFirstName}]] [[${assessment.creatorLastName}]] added an assessment to thesis "[[${thesis.title}]]" @@ -338,11 +324,7 @@ Review Applications:

-
-
- Manage your notification settings here -
-

', 'Assessment was added to a submitted thesis'), ('THESIS_CLOSED', 'Thesis Closed', '

Dear [[${recipient.firstName}]],

+
', 'Assessment was added to a submitted thesis'), ('THESIS_CLOSED', 'Thesis Closed', '

Dear [[${recipient.firstName}]],

[[${deletingUser.firstName}]] [[${deletingUser.lastName}]] closed thesis "[[${thesis.title}]]". @@ -353,11 +335,7 @@ Please contact your supervisor or examiner if you think that this was a mistake. Full Details:

-
-
- Manage your notification settings here -
-

', 'Thesis was closed before completion'), ('THESIS_COMMENT_POSTED', 'A Comment was posted', '

Dear [[${recipient.firstName}]],

+
', 'Thesis was closed before completion'), ('THESIS_COMMENT_POSTED', 'A Comment was posted', '

Dear [[${recipient.firstName}]],

[[${comment.creatorFirstName}]] [[${comment.creatorLastName}]] posted a comment on thesis "[[${thesis.title}]]" @@ -372,11 +350,7 @@ Please contact your supervisor or examiner if you think that this was a mistake. Full Details:

-
-
- Manage your notification settings here -
-

', 'New comment on a thesis. TO depends whether it''s a student or supervisor comment'), ('THESIS_CREATED', 'Thesis Created', '

Dear [[${recipient.firstName}]],

+
', 'New comment on a thesis. TO depends whether it''s a student or supervisor comment'), ('THESIS_CREATED', 'Thesis Created', '

Dear [[${recipient.firstName}]],

[[${creatingUser.firstName}]] [[${creatingUser.lastName}]] created and assigned a thesis to you: [[${thesisUrl}]] @@ -391,7 +365,9 @@ Please contact your supervisor or examiner if you think that this was a mistake.

The next step is that you write a proposal and submit it on [[${thesisUrl}]] -

', 'New thesis was created and assigned to a student'), ('THESIS_FINAL_GRADE', 'Final Grade available for Thesis', '

Dear [[${recipient.firstName}]],

+

+ +
', 'New thesis was created and assigned to a student'), ('THESIS_FINAL_GRADE', 'Final Grade available for Thesis', '

Dear [[${recipient.firstName}]],

[[${thesis.examiners}]] added the final grade to your thesis "[[${thesis.title}]]" @@ -410,11 +386,7 @@ The next step is that you write a proposal and submit it on

-
-
- Manage your notification settings here -
-

', 'Final grade was added to a thesis'), ('THESIS_FINAL_SUBMISSION', 'Thesis Submitted', '

Dear [[${recipient.firstName}]],

+
', 'Final grade was added to a thesis'), ('THESIS_FINAL_SUBMISSION', 'Thesis Submitted', '

Dear [[${recipient.firstName}]],

[[${thesis.students}]] submitted thesis "[[${thesis.title}]]". @@ -428,11 +400,7 @@ The next step is to write an assessment about the thesis. Full Details:

-
-
- Manage your notification settings here -
-

', 'Student submitted final thesis'), ('THESIS_PRESENTATION_DELETED', 'Presentation deleted', '

Dear [[${recipient.firstName}]],

+
', 'Student submitted final thesis'), ('THESIS_PRESENTATION_DELETED', 'Presentation deleted', '

Dear [[${recipient.firstName}]],

[[${deletingUser.firstName}]] [[${deletingUser.lastName}]] cancelled the presentation scheduled at [[${presentation.scheduledAt}]] for thesis "[[${thesis.title}]]" @@ -442,11 +410,7 @@ The next step is to write an assessment about the thesis. Full Details:

-
-
- Manage your notification settings here -
-

', 'Scheduled presentation was deleted'), ('THESIS_PRESENTATION_SCHEDULED', 'New Presentation scheduled', '

Dear [[${recipient.firstName}]],

+
', 'Scheduled presentation was deleted'), ('THESIS_PRESENTATION_SCHEDULED', 'New Presentation scheduled', '

Dear [[${recipient.firstName}]],

[[${presentation.creatorFirstName}]] [[${presentation.creatorLastName}]] scheduled a presentation for thesis "[[${thesis.title}]]" @@ -481,11 +445,7 @@ The next step is to write an assessment about the thesis. Full Details:

-
-
- Manage your notification settings here -
-

', 'New presentation was scheduled'), ('THESIS_PRESENTATION_UPDATED', 'Presentation updated', '

Dear [[${recipient.firstName}]],

+
', 'New presentation was scheduled'), ('THESIS_PRESENTATION_UPDATED', 'Presentation updated', '

Dear [[${recipient.firstName}]],

[[${presentation.creatorFirstName}]] [[${presentation.creatorLastName}]] updated a presentation for thesis "[[${thesis.title}]]" @@ -520,14 +480,7 @@ The next step is to write an assessment about the thesis. Full Details:

-
-
- Manage your notification settings here -
-

', 'Presentation was updated'), ('THESIS_PRESENTATION_INVITATION', 'Thesis Presentation Invitation', '

INVITATION

As part of their [[${thesis.type}]]''s thesis
[[${thesis.students}]]
will give their [[${presentation.type}]] presentation on
[[${presentation.scheduledAt}]]
online at [[${presentation.streamUrl}]]
and onsite in [[${presentation.location}]]

Title:
[[${thesis.title}]]

Examiner: [[${thesis.examiners}]]
Supervisor(s): [[${thesis.supervisors}]]

The presentation will be in [[${presentation.language}]]. Everybody is cordially invited to attend.

Abstract

Full Details: [[${presentationUrl}]]


-
Manage your notification settings here
', 'Public Presentation Invitation'), ('THESIS_PRESENTATION_INVITATION_CANCELLED', 'Thesis Presentation Cancelled', '

Dear [[${recipient.firstName}]],

The [[${thesis.type}]] thesis presentation of [[${thesis.students}]] scheduled at [[${presentation.scheduledAt}]] was cancelled.


-
Manage your notification settings here
', 'Public Presentation was deleted'), ('THESIS_PRESENTATION_INVITATION_UPDATED', 'Thesis Presentation Updated', '

INVITATION

As part of their [[${thesis.type}]]''s thesis
[[${thesis.students}]]
will give their [[${presentation.type}]] presentation on
[[${presentation.scheduledAt}]]
online at [[${presentation.streamUrl}]]
and onsite in [[${presentation.location}]]

Title:
[[${thesis.title}]]

Examiner: [[${thesis.examiners}]]
Supervisor(s): [[${thesis.supervisors}]]

The presentation will be in [[${presentation.language}]]. Everybody is cordially invited to attend.

Abstract

Full Details: [[${presentationUrl}]]


-
Manage your notification settings here
', 'Public Presentation was updated'), ('THESIS_PROPOSAL_ACCEPTED', 'Thesis Proposal Accepted', '

Dear [[${recipient.firstName}]],

+
', 'Presentation was updated'), ('THESIS_PRESENTATION_INVITATION', 'Thesis Presentation Invitation', '

INVITATION

As part of their [[${thesis.type}]]''s thesis
[[${thesis.students}]]
will give their [[${presentation.type}]] presentation on
[[${presentation.scheduledAt}]]
online at [[${presentation.streamUrl}]]
and onsite in [[${presentation.location}]]

Title:
[[${thesis.title}]]

Examiner: [[${thesis.examiners}]]
Supervisor(s): [[${thesis.supervisors}]]

The presentation will be in [[${presentation.language}]]. Everybody is cordially invited to attend.

Abstract

Full Details: [[${presentationUrl}]]

', 'Public Presentation Invitation'), ('THESIS_PRESENTATION_INVITATION_CANCELLED', 'Thesis Presentation Cancelled', '

Dear [[${recipient.firstName}]],

The [[${thesis.type}]] thesis presentation of [[${thesis.students}]] scheduled at [[${presentation.scheduledAt}]] was cancelled.

', 'Public Presentation was deleted'), ('THESIS_PRESENTATION_INVITATION_UPDATED', 'Thesis Presentation Updated', '

INVITATION

As part of their [[${thesis.type}]]''s thesis
[[${thesis.students}]]
will give their [[${presentation.type}]] presentation on
[[${presentation.scheduledAt}]]
online at [[${presentation.streamUrl}]]
and onsite in [[${presentation.location}]]

Title:
[[${thesis.title}]]

Examiner: [[${thesis.examiners}]]
Supervisor(s): [[${thesis.supervisors}]]

The presentation will be in [[${presentation.language}]]. Everybody is cordially invited to attend.

Abstract

Full Details: [[${presentationUrl}]]

', 'Public Presentation was updated'), ('THESIS_PROPOSAL_ACCEPTED', 'Thesis Proposal Accepted', '

Dear [[${recipient.firstName}]],

[[${proposal.approverFirstName}]] [[${proposal.approverLastName}]] approved the proposal of thesis "[[${thesis.title}]]". @@ -535,11 +488,7 @@ The next step is to start with the project work and with writing the thesis. You can see your submission deadline on [[${thesisUrl}]].

-
-
- Manage your notification settings here -
-

', 'Proposal was accepted'), ('THESIS_PROPOSAL_REJECTED', 'Changes were requested for Proposal', '

Dear [[${recipient.firstName}]],

+
', 'Proposal was accepted'), ('THESIS_PROPOSAL_REJECTED', 'Changes were requested for Proposal', '

Dear [[${recipient.firstName}]],

[[${reviewingUser.firstName}]] [[${reviewingUser.lastName}]] reviewed your proposal for thesis "[[${thesis.title}]]". @@ -556,11 +505,7 @@ The following changes were requested:
Full Details:

-
-
- Manage your notification settings here -
-

', 'Changes were requested for proposal'), ('THESIS_PROPOSAL_UPLOADED', 'Thesis Proposal Added', '

Dear [[${recipient.firstName}]],

+
', 'Changes were requested for proposal'), ('THESIS_PROPOSAL_UPLOADED', 'Thesis Proposal Added', '

Dear [[${recipient.firstName}]],

[[${proposal.creatorFirstName}]] [[${proposal.creatorLastName}]] uploaded a proposal to thesis "[[${thesis.title}]]". @@ -571,11 +516,7 @@ You can find the submitted file in the attachment part of this email. Full Details:

-
-
- Manage your notification settings here -
-

', 'Student uploaded new proposal')) AS v(template_case, subject, body_html, description)) +
', 'Student uploaded new proposal')) AS v(template_case, subject, body_html, description)) INSERT INTO email_templates (email_template_id, research_group_id, From 0efdf83764d6178ec66784910551478696bfe84c Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Sat, 9 May 2026 15:47:50 +0200 Subject: [PATCH 2/2] Address review feedback on the email-footer PR (#891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five threads from review: 1. CRITICAL — Liquibase checksum (Claudia). The seed file `insert_tum_default_email_templates.sql` is loaded via `sqlFile` from the already-applied `ramona:21-seed-default-email-templates` changeset, with no `runOnChange`. Modifying its content changes the MD5 Liquibase validates on startup, breaking deployed databases. Revert the file to develop's content. Migration 40 already rewrites the literal "Best regards / Coordination Team" sign-off to `${emailSignature}` via REPLACE for both fresh and existing DBs, so the end state is identical. 2. Notification-link href in MailBuilder.send() interpolated `config.getClientHost()` directly into an HTML attribute. HTML-escape the URL via Spring's HtmlUtils and add `rel="noopener noreferrer nofollow"` for parity with the other Anchor tags in the codebase. 3. prepareMinimalApplicationMailBuilder did not include the `${emailSignature}` placeholder, so emails sent through the minimal path skipped the per-research-group signature. Append the placeholder so the consistent-footer behaviour holds for all mail paths. 4. (Self) Server-rendered HTML signature is admin-supplied — a known trust-boundary choice for a per-group signature feature, intentionally not sanitized so admins can embed images/links. 5. (Doc-only) PR description references migration #36; the migration was renumbered to #40 during the develop merge — to be updated on the PR. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../aet/thesis/service/MailingService.java | 6 ++++- .../cit/aet/thesis/utility/MailBuilder.java | 7 +++++- .../insert_tum_default_email_templates.sql | 24 ++++++++++++------- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/de/tum/cit/aet/thesis/service/MailingService.java b/server/src/main/java/de/tum/cit/aet/thesis/service/MailingService.java index 88bc13eb3..b8eb89b0c 100644 --- a/server/src/main/java/de/tum/cit/aet/thesis/service/MailingService.java +++ b/server/src/main/java/de/tum/cit/aet/thesis/service/MailingService.java @@ -168,7 +168,11 @@ private MailBuilder prepareMinimalApplicationMailBuilder(Application application + "

" + "

You can view the full application details here: " + "" - + applicationUrl + "

"; + + applicationUrl + "

" + // Match the rest of the templates (which all end with the + // emailSignature placeholder so the per-research-group + // signature gets injected by Thymeleaf). + + "
"; MailBuilder builder = new MailBuilder(config, subject, body); applyGroupSignature(builder, application.getResearchGroup()); diff --git a/server/src/main/java/de/tum/cit/aet/thesis/utility/MailBuilder.java b/server/src/main/java/de/tum/cit/aet/thesis/utility/MailBuilder.java index a19875713..0182c5c88 100644 --- a/server/src/main/java/de/tum/cit/aet/thesis/utility/MailBuilder.java +++ b/server/src/main/java/de/tum/cit/aet/thesis/utility/MailBuilder.java @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.web.util.HtmlUtils; import org.thymeleaf.context.Context; import jakarta.activation.DataHandler; @@ -588,9 +589,13 @@ public void send(JavaMailSender mailSender, UploadService uploadService) { BodyPart messageBody = new MimeBodyPart(); String renderedHtml = config.getTemplateEngine().process(templateHtml, templateContext); + // HTML-escape the client host before interpolating it into an + // href so a misconfigured CHAIR_URL can't break the attribute + // or open an HTML-injection vector in outgoing mail. + String settingsUrl = HtmlUtils.htmlEscape(config.getClientHost() + "/settings/notifications"); renderedHtml += "
" + "You can (un)subscribe to similar emails in your " - + "" + + "" + "notification settings in Thesis Management.
"; messageBody.setContent(renderedHtml, "text/html; charset=utf-8"); messageContent.addBodyPart(messageBody); diff --git a/server/src/main/resources/db/changelog/manual/insert_tum_default_email_templates.sql b/server/src/main/resources/db/changelog/manual/insert_tum_default_email_templates.sql index 4f386ef46..411a9f07a 100644 --- a/server/src/main/resources/db/changelog/manual/insert_tum_default_email_templates.sql +++ b/server/src/main/resources/db/changelog/manual/insert_tum_default_email_templates.sql @@ -49,7 +49,8 @@ FROM defaults d You can view your thesis details and tasks on: [[${thesisUrl}]]

-
+

Best regards,
+The Thesis Coordination Team

', 'Application was accepted with different advisor and supervisor'), ('APPLICATION_ACCEPTED_NO_ADVISOR', 'Thesis Application Acceptance', '

Dear [[${recipient.firstName}]],

@@ -76,7 +77,8 @@ thesis. You can view your thesis details and tasks on: [[${thesisUrl}]]

-
', 'Application was accepted with same advisor and supervisor'), ('APPLICATION_CREATED_CHAIR', 'New Thesis Application', '

Dear [[${recipient.firstName}]],

+

Best regards,
+The Thesis Coordination Team

', 'Application was accepted with same advisor and supervisor'), ('APPLICATION_CREATED_CHAIR', 'New Thesis Application', '

Dear [[${recipient.firstName}]],

there is a new thesis application by [[${application.applicantFirstName}]] [[${application.applicantLastName}]].

We received the following thesis application details:

@@ -222,7 +224,8 @@ The volume of applications received this year was exceptionally high, and I have I recommend exploring other supervisors or research groups who may align more closely with your qualifications and area of interest.

-
', 'Application was rejected'), ('APPLICATION_REJECTED_TOPIC_REQUIREMENTS', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

+

Best regards,
+The Thesis Coordination Team

', 'Application was rejected'), ('APPLICATION_REJECTED_TOPIC_REQUIREMENTS', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

Thank you for your interest in pursuing your thesis under my supervision. @@ -233,7 +236,8 @@ After reviewing your application and supporting documents, I regret to inform yo I recommend considering other thesis topics or reaching out to other research groups whose research focus may better align with your qualifications.

-
', 'Application was rejected because topic requirements were not met'), ('APPLICATION_REJECTED_STUDENT_REQUIREMENTS', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

+

Best regards,
+The Thesis Coordination Team

', 'Application was rejected because topic requirements were not met'), ('APPLICATION_REJECTED_STUDENT_REQUIREMENTS', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

Thank you for your interest in pursuing your thesis under my supervision. @@ -245,7 +249,8 @@ Unfortunately, I must inform you that you do not currently meet the necessary re I recommend exploring other supervisors or research groups who may align more closely with your qualifications and area of interest.

-
', 'Application was rejected because student does not fulfil chair''s requirements'), ('APPLICATION_REJECTED_TITLE_NOT_INTERESTING', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

+

Best regards,
+The Thesis Coordination Team

', 'Application was rejected because student does not fulfil chair''s requirements'), ('APPLICATION_REJECTED_TITLE_NOT_INTERESTING', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

Thank you for your interest in pursuing your thesis under my supervision. @@ -257,7 +262,8 @@ However, the suggested topic does not align with the current research interests I encourage you to explore other research groups whose research is more closely related to your proposed area of study.

-
', 'Application was rejected because the suggested thesis title is not interesting'), ('APPLICATION_REJECTED_TOPIC_FILLED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

+

Best regards,
+The Thesis Coordination Team

', 'Application was rejected because the suggested thesis title is not interesting'), ('APPLICATION_REJECTED_TOPIC_FILLED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

Thank you for your interest in pursuing your thesis under my supervision. @@ -272,7 +278,8 @@ We found a student for the specific topic you applied for. You can explore other topics or suggest a topic yourself in your area of interest.

-
', 'Application was rejected because topic was closed'), ('APPLICATION_REJECTED_TOPIC_OUTDATED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

+

Best regards,
+The Thesis Coordination Team

', 'Application was rejected because topic was closed'), ('APPLICATION_REJECTED_TOPIC_OUTDATED', 'Thesis Application Rejection', '

Dear [[${recipient.firstName}]],

Thank you for your interest in pursuing your thesis under my supervision. @@ -287,7 +294,8 @@ I wanted to inform you that the topic you applied for is no longer available. You can explore other topics or suggest a topic yourself in your area of interest.

-
', 'Application was rejected because topic is outdated'), ('APPLICATION_REMINDER', 'Unreviewed Thesis Applications', '

Dear [[${recipient.firstName}]],

+

Best regards,
+The Thesis Coordination Team

', 'Application was rejected because topic is outdated'), ('APPLICATION_REMINDER', 'Unreviewed Thesis Applications', '

Dear [[${recipient.firstName}]],

There are currently [[${unreviewedApplications}]] unreviewed thesis applications.