Skip to content

Commit e30dc9e

Browse files
authored
Improve email message readability (#3950)
1 parent 4b1185f commit e30dc9e

File tree

6 files changed

+39
-21
lines changed

6 files changed

+39
-21
lines changed

Backend.Tests/Controllers/InviteControllerTests.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public async Task Setup()
4646
new UserRoleRepositoryMock(), new EmailServiceMock(), permissionService);
4747
_inviteController = new InviteController(_projRepo, inviteService, permissionService);
4848

49+
var _userId = (await _userRepo.Create(new() { Name = "Signore Inviter", Username = "inviter" }))!.Id;
50+
_inviteController.ControllerContext.HttpContext = PermissionServiceMock.HttpContextWithUserId(_userId);
51+
4952
_projId = (await _projRepo.Create(new Project { Name = ProjectName }))!.Id;
5053
var inviteActive =
5154
new ProjectInvite(_projId, EmailActive, Role.Harvester) { Created = DateTime.UtcNow };
@@ -56,7 +59,7 @@ public async Task Setup()
5659
await inviteRepo.Insert(inviteExpired);
5760
_tokenExpired = inviteExpired.Token;
5861
var inviteFuture =
59-
new ProjectInvite(_projId, EmailExpired, Role.Harvester) { Created = DateTime.UtcNow.AddYears(1) };
62+
new ProjectInvite(_projId, EmailFuture, Role.Harvester) { Created = DateTime.UtcNow.AddYears(1) };
6063
await inviteRepo.Insert(inviteFuture);
6164
_tokenFuture = inviteFuture.Token;
6265
}

Backend/Controllers/InviteController.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ public async Task<IActionResult> EmailInviteToProject([FromBody, BindRequired] E
3737
return Forbid();
3838
}
3939

40+
var inviterId = _permissionService.GetUserId(HttpContext);
41+
4042
var project = await _projRepo.GetProject(projectId);
4143
if (project is null)
4244
{
4345
return NotFound($"projectId: {projectId}");
4446
}
4547

46-
return Ok(await _inviteService.EmailLink(project, data.Role, data.EmailAddress, data.Message));
48+
return Ok(await _inviteService.EmailLink(project, data.Role, data.EmailAddress, inviterId, data.Message));
4749
}
4850

4951
/// <summary> Validates invite token in url and adds user to project </summary>

Backend/Interfaces/IInviteService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace BackendFramework.Interfaces
55
{
66
public interface IInviteService
77
{
8-
Task<string> EmailLink(Project project, Role role, string emailAddress, string message);
8+
Task<string> EmailLink(Project project, Role role, string emailAddress, string inviterId, string message);
99
Task<EmailInviteStatus> ValidateProjectToken(string projectId, string token);
1010
}
1111
}

Backend/Services/EmailVerifyService.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ private MimeMessage CreateEmail(User user, string url)
6060
message.Subject = "The Combine email verification";
6161
message.Body = new TextPart("plain")
6262
{
63-
Text = $"Email verification has been requested for the user {user.Username}. " +
64-
$"Follow the link to verify {user.Username}'s email address: {url}\n\n" +
65-
"Email verification is required to add users to your projects in The Combine." +
66-
$"(This link will expire in {_expireTime.TotalMinutes} minutes.)\n\n" +
63+
Text = $"Email verification has been requested for {user.Name} (username: {user.Username}).\n\n" +
64+
"Email verification is required to add users to your projects in The Combine.\n\n" +
65+
$"Follow this link to verify your email address: {url}\n\n" +
66+
$"(Link will expire in {_expireTime.TotalMinutes} minutes.)\n\n" +
6767
"If you do not wish to verify your email address, you may ignore this email."
6868
};
6969
return message;

Backend/Services/InviteService.cs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public class InviteService(IOptions<Startup.Settings> options, IInviteRepository
1919
private readonly IEmailService _emailService = emailService;
2020
private readonly IPermissionService _permissionService = permissionService;
2121

22+
private const int MaxInviteMessageLength = 1000;
23+
2224
internal static string CreateLink(ProjectInvite invite)
2325
{
2426
// Matches the Path.ProjInvite route in src\router\appRoutes.tsx
@@ -32,27 +34,38 @@ internal async Task<ProjectInvite> CreateProjectInvite(string projectId, Role ro
3234
return invite;
3335
}
3436

35-
private MimeMessage CreateEmail(string emailAddress, string emailMessage, string link, string projectName)
37+
private MimeMessage CreateEmail(
38+
string emailAddress, string emailMessage, string inviter, string link, string projectName)
3639
{
40+
// Trim user-provided emailMessage
41+
var trimmedMessage = emailMessage.Trim();
42+
if (trimmedMessage.Length > MaxInviteMessageLength)
43+
{
44+
trimmedMessage = trimmedMessage.Substring(0, MaxInviteMessageLength);
45+
}
46+
3747
var message = new MimeMessage();
3848
message.To.Add(new MailboxAddress("FutureCombineUser", emailAddress));
39-
message.Subject = "The Combine Project Invite";
40-
message.Body = new TextPart("plain")
49+
message.Subject = "The Combine project invitation";
50+
message.Body = new TextPart("plain") // With "plain", we don't need to sanitize emailMessage.
4151
{
42-
Text = $"You have been invited project '{projectName}' on The Combine.\n" +
43-
$"To become a member of this project, go to {link}.\n" +
44-
$"Use this email address during registration: {emailAddress}.\n\n" +
45-
$"Message from Project Admin: {emailMessage}\n\n" +
46-
$"(This link will expire in {_expireTime.TotalDays} days.)\n\n" +
47-
"If you did not expect an invite please ignore this email."
52+
Text = $"You have been invited to project '{projectName}' on The Combine.\n\n" +
53+
$"Follow this link to become a member of the project: {link}\n\n" +
54+
$"(Link will expire in {_expireTime.TotalDays} days.)\n\n" +
55+
$"Use this email address during registration: {emailAddress}\n\n" +
56+
"If you did not expect an invite, please ignore this email.\n\n" +
57+
$"Message from project administrator ({inviter}):\n\n" +
58+
trimmedMessage
4859
};
4960
return message;
5061
}
5162

52-
public async Task<string> EmailLink(Project project, Role role, string emailAddress, string message)
63+
public async Task<string> EmailLink(
64+
Project project, Role role, string emailAddress, string inviterId, string message)
5365
{
5466
var link = CreateLink(await CreateProjectInvite(project.Id, role, emailAddress));
55-
await _emailService.SendEmail(CreateEmail(emailAddress, message, link, project.Name));
67+
var inviter = await _userRepo.GetUser(inviterId) ?? throw new InviteException("Inviting user not found.");
68+
await _emailService.SendEmail(CreateEmail(emailAddress, message, inviter.Name, link, project.Name));
5669
return link;
5770
}
5871

Backend/Services/PasswordResetService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ private MimeMessage CreateEmail(User user, string url)
8282
message.Subject = "The Combine password reset";
8383
message.Body = new TextPart("plain")
8484
{
85-
Text = $"A password reset has been requested for the user {user.Username}. " +
86-
$"Follow this link to reset {user.Username}'s password: {url}\n\n" +
87-
$"(This link will expire in {_expireTime.TotalMinutes} minutes.)\n\n" +
85+
Text = $"A password reset has been requested for {user.Name} (username: {user.Username}).\n\n" +
86+
$"Follow this link to reset your password: {url}\n\n" +
87+
$"(Link will expire in {_expireTime.TotalMinutes} minutes.)\n\n" +
8888
"If you did not request a password reset, please ignore this email."
8989
};
9090
return message;

0 commit comments

Comments
 (0)