add email sender service

This commit is contained in:
cuqmbr 2025-05-29 13:13:41 +03:00
parent 41158b34c5
commit 68a9e06eeb
Signed by: cuqmbr
GPG Key ID: 0AA446880C766199
11 changed files with 238 additions and 20 deletions

View File

@ -0,0 +1,7 @@
namespace cuqmbr.TravelGuide.Application.Common.Services;
public interface EmailSenderService
{
Task SendAsync(string[] addresses, string subject, string body,
CancellationToken cancellationToken);
}

View File

@ -164,11 +164,25 @@
"Microsoft.Extensions.Options": "8.0.0" "Microsoft.Extensions.Options": "8.0.0"
} }
}, },
"BouncyCastle.Cryptography": {
"type": "Transitive",
"resolved": "2.5.1",
"contentHash": "zy8TMeTP+1FH2NrLaNZtdRbBdq7u5MI+NFZQOBSM69u5RFkciinwzV2eveY6Kjf5MzgsYvvl6kTStsj3JrXqkg=="
},
"FluentValidation": { "FluentValidation": {
"type": "Transitive", "type": "Transitive",
"resolved": "11.11.0", "resolved": "11.11.0",
"contentHash": "cyIVdQBwSipxWG8MA3Rqox7iNbUNUTK5bfJi9tIdm4CAfH71Oo5ABLP4/QyrUwuakqpUEPGtE43BDddvEehuYw==" "contentHash": "cyIVdQBwSipxWG8MA3Rqox7iNbUNUTK5bfJi9tIdm4CAfH71Oo5ABLP4/QyrUwuakqpUEPGtE43BDddvEehuYw=="
}, },
"MailKit": {
"type": "Transitive",
"resolved": "4.12.1",
"contentHash": "rIqJm92qtHvk1hDchsJ95Hy7n46A7imE24ol++ikXBsjf3Bi1qDBu4H91FfY6LrYXJaxRlc2gIIpC8AOJrCbqg==",
"dependencies": {
"MimeKit": "4.12.0",
"System.Formats.Asn1": "8.0.1"
}
},
"MediatR": { "MediatR": {
"type": "Transitive", "type": "Transitive",
"resolved": "12.4.1", "resolved": "12.4.1",
@ -701,6 +715,15 @@
"System.Security.Principal.Windows": "4.5.0" "System.Security.Principal.Windows": "4.5.0"
} }
}, },
"MimeKit": {
"type": "Transitive",
"resolved": "4.12.0",
"contentHash": "PFUHfs6BZxKYM/QPJksAwXphbJf0SEfdSfsoQ6p6yvFRaJPofFJMBiotWhFRrdSUzfp6C6K49EjBIqIwZ2TJqA==",
"dependencies": {
"BouncyCastle.Cryptography": "2.5.1",
"System.Security.Cryptography.Pkcs": "8.0.1"
}
},
"Newtonsoft.Json": { "Newtonsoft.Json": {
"type": "Transitive", "type": "Transitive",
"resolved": "13.0.3", "resolved": "13.0.3",
@ -769,6 +792,11 @@
"resolved": "5.0.0", "resolved": "5.0.0",
"contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg==" "contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
}, },
"System.Formats.Asn1": {
"type": "Transitive",
"resolved": "8.0.1",
"contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A=="
},
"System.IdentityModel.Tokens.Jwt": { "System.IdentityModel.Tokens.Jwt": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.1", "resolved": "8.0.1",
@ -845,6 +873,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Application": "[1.0.0, )", "Application": "[1.0.0, )",
"MailKit": "[4.12.1, )",
"Microsoft.Extensions.Http": "[9.0.4, )", "Microsoft.Extensions.Http": "[9.0.4, )",
"Newtonsoft.Json": "[13.0.3, )" "Newtonsoft.Json": "[13.0.3, )"
} }

View File

@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using cuqmbr.TravelGuide.Application.Common.Services; using cuqmbr.TravelGuide.Application.Common.Services;
using cuqmbr.TravelGuide.Application.Common.Persistence;
namespace cuqmbr.TravelGuide.HttpApi.Controllers; namespace cuqmbr.TravelGuide.HttpApi.Controllers;
@ -9,15 +8,13 @@ namespace cuqmbr.TravelGuide.HttpApi.Controllers;
public class TestsController : ControllerBase public class TestsController : ControllerBase
{ {
private readonly IStringLocalizer _localizer; private readonly IStringLocalizer _localizer;
private readonly UnitOfWork _unitOfWork; private readonly EmailSenderService _emailSender;
public TestsController( public TestsController(SessionCultureService cultureService,
SessionCultureService cultureService, IStringLocalizer localizer, EmailSenderService emailSender)
IStringLocalizer localizer,
UnitOfWork unitOfWork)
{ {
_localizer = localizer; _localizer = localizer;
_unitOfWork = unitOfWork; _emailSender = emailSender;
} }
[HttpGet("getLocalizedString/{inputString}")] [HttpGet("getLocalizedString/{inputString}")]
@ -31,19 +28,15 @@ public class TestsController : ControllerBase
[HttpGet("trigger")] [HttpGet("trigger")]
public async Task Trigger(CancellationToken cancellationToken) public async Task Trigger(CancellationToken cancellationToken)
{ {
// await _unitOfWork.BusRepository.AddOneAsync( var body =
// new Domain.Entities.Bus() @"Hello, friend!
// {
// Number = "AB1234MK",
// Model = "This is a fancy bus model",
// Capacity = 40
// },
// cancellationToken);
//
// await _unitOfWork.SaveAsync(cancellationToken);
// _unitOfWork.Dispose();
var vehicles = await _unitOfWork.VehicleRepository This is my email message for you.
.GetPageAsync(1, 10, cancellationToken);
--
Travel Guide Service
";
await _emailSender.SendAsync(new string[] { "cuqmbr@ya.ru" }, "Test subject", body, cancellationToken);
} }
} }

View File

@ -27,5 +27,16 @@
"PublicKey": "sandbox_xxxxxxxxxxxx", "PublicKey": "sandbox_xxxxxxxxxxxx",
"PrivateKey": "sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "PrivateKey": "sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
} }
},
"Email": {
"Smtp": {
"Host": "mail.travel-guide.cuqmbr.xyz",
"Port": "465",
"UseTls": true,
"Username": "no-reply",
"Password": "super-secret-password",
"SenderAddress": "no-reply@travel-guide.cuqmbr.xyz",
"SenderName": "Travel Guide"
}
} }
} }

View File

@ -27,5 +27,16 @@
"PublicKey": "sandbox_xxxxxxxxxxxx", "PublicKey": "sandbox_xxxxxxxxxxxx",
"PrivateKey": "sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "PrivateKey": "sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
} }
},
"Email": {
"Smtp": {
"Host": "mail.travel-guide.cuqmbr.xyz",
"Port": "465",
"UseTls": true,
"Username": "no-reply",
"Password": "super-secret-password",
"SenderAddress": "no-reply@travel-guide.cuqmbr.xyz",
"SenderName": "Travel Guide"
}
} }
} }

View File

@ -106,6 +106,11 @@
"Microsoft.Extensions.Options": "8.0.0" "Microsoft.Extensions.Options": "8.0.0"
} }
}, },
"BouncyCastle.Cryptography": {
"type": "Transitive",
"resolved": "2.5.1",
"contentHash": "zy8TMeTP+1FH2NrLaNZtdRbBdq7u5MI+NFZQOBSM69u5RFkciinwzV2eveY6Kjf5MzgsYvvl6kTStsj3JrXqkg=="
},
"FluentValidation": { "FluentValidation": {
"type": "Transitive", "type": "Transitive",
"resolved": "11.11.0", "resolved": "11.11.0",
@ -125,6 +130,15 @@
"resolved": "2.14.1", "resolved": "2.14.1",
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
}, },
"MailKit": {
"type": "Transitive",
"resolved": "4.12.1",
"contentHash": "rIqJm92qtHvk1hDchsJ95Hy7n46A7imE24ol++ikXBsjf3Bi1qDBu4H91FfY6LrYXJaxRlc2gIIpC8AOJrCbqg==",
"dependencies": {
"MimeKit": "4.12.0",
"System.Formats.Asn1": "8.0.1"
}
},
"MediatR": { "MediatR": {
"type": "Transitive", "type": "Transitive",
"resolved": "12.4.1", "resolved": "12.4.1",
@ -848,6 +862,15 @@
"System.Security.Principal.Windows": "4.5.0" "System.Security.Principal.Windows": "4.5.0"
} }
}, },
"MimeKit": {
"type": "Transitive",
"resolved": "4.12.0",
"contentHash": "PFUHfs6BZxKYM/QPJksAwXphbJf0SEfdSfsoQ6p6yvFRaJPofFJMBiotWhFRrdSUzfp6C6K49EjBIqIwZ2TJqA==",
"dependencies": {
"BouncyCastle.Cryptography": "2.5.1",
"System.Security.Cryptography.Pkcs": "8.0.1"
}
},
"Mono.TextTemplating": { "Mono.TextTemplating": {
"type": "Transitive", "type": "Transitive",
"resolved": "3.0.0", "resolved": "3.0.0",
@ -982,6 +1005,11 @@
"System.Composition.Runtime": "7.0.0" "System.Composition.Runtime": "7.0.0"
} }
}, },
"System.Formats.Asn1": {
"type": "Transitive",
"resolved": "8.0.1",
"contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A=="
},
"System.IdentityModel.Tokens.Jwt": { "System.IdentityModel.Tokens.Jwt": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.1", "resolved": "8.0.1",
@ -1109,6 +1137,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Application": "[1.0.0, )", "Application": "[1.0.0, )",
"MailKit": "[4.12.1, )",
"Microsoft.Extensions.Http": "[9.0.4, )", "Microsoft.Extensions.Http": "[9.0.4, )",
"Newtonsoft.Json": "[13.0.3, )" "Newtonsoft.Json": "[13.0.3, )"
} }

View File

@ -5,6 +5,8 @@ public sealed class ConfigurationOptions
public static string SectionName { get; } = ""; public static string SectionName { get; } = "";
public PaymentProcessingConfigurationOptions PaymentProcessing { get; set; } = new(); public PaymentProcessingConfigurationOptions PaymentProcessing { get; set; } = new();
public EmailConfigurationOptions Email { get; set; } = new();
} }
public sealed class PaymentProcessingConfigurationOptions public sealed class PaymentProcessingConfigurationOptions
@ -22,3 +24,25 @@ public sealed class LiqPayConfigurationOptions
public string PrivateKey { get; set; } public string PrivateKey { get; set; }
} }
public sealed class EmailConfigurationOptions
{
public SmtpConfigurationOptions Smtp { get; set; } = new();
}
public sealed class SmtpConfigurationOptions
{
public string Host { get; set; }
public ushort Port { get; set; }
public bool UseTls { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string SenderAddress { get; set; }
public string SenderName { get; set; }
}

View File

@ -11,6 +11,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MailKit" Version="4.12.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.4" /> <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,50 @@
using cuqmbr.TravelGuide.Application.Common.Services;
using MailKit.Net.Smtp;
using Microsoft.Extensions.Options;
using MimeKit;
namespace cuqmbr.TravelGuide.Infrastructure.Services;
public sealed class MailKitEmailSenderService : EmailSenderService
{
private readonly SmtpConfigurationOptions _configuration;
public MailKitEmailSenderService(
IOptions<ConfigurationOptions> configuration)
{
_configuration = configuration.Value.Email.Smtp;
}
public async Task SendAsync(string[] addresses, string subject,
string body, CancellationToken cancellationToken)
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress(
_configuration.SenderName, _configuration.SenderAddress));
foreach (var address in addresses)
{
message.To.Add(new MailboxAddress("", address));
}
message.Subject = subject;
message.Body = new TextPart("plain")
{
Text = body
};
using var client = new SmtpClient();
await client.ConnectAsync(_configuration.Host,
_configuration.Port, _configuration.UseTls,
cancellationToken);
await client.AuthenticateAsync(_configuration.Username,
_configuration.Password, cancellationToken);
await client.SendAsync(message, cancellationToken);
await client.DisconnectAsync(true, cancellationToken);
}
}

View File

@ -2,6 +2,16 @@
"version": 1, "version": 1,
"dependencies": { "dependencies": {
"net9.0": { "net9.0": {
"MailKit": {
"type": "Direct",
"requested": "[4.12.1, )",
"resolved": "4.12.1",
"contentHash": "rIqJm92qtHvk1hDchsJ95Hy7n46A7imE24ol++ikXBsjf3Bi1qDBu4H91FfY6LrYXJaxRlc2gIIpC8AOJrCbqg==",
"dependencies": {
"MimeKit": "4.12.0",
"System.Formats.Asn1": "8.0.1"
}
},
"Microsoft.Extensions.Http": { "Microsoft.Extensions.Http": {
"type": "Direct", "type": "Direct",
"requested": "[9.0.4, )", "requested": "[9.0.4, )",
@ -40,6 +50,11 @@
"Microsoft.Extensions.Options": "8.0.0" "Microsoft.Extensions.Options": "8.0.0"
} }
}, },
"BouncyCastle.Cryptography": {
"type": "Transitive",
"resolved": "2.5.1",
"contentHash": "zy8TMeTP+1FH2NrLaNZtdRbBdq7u5MI+NFZQOBSM69u5RFkciinwzV2eveY6Kjf5MzgsYvvl6kTStsj3JrXqkg=="
},
"FluentValidation": { "FluentValidation": {
"type": "Transitive", "type": "Transitive",
"resolved": "11.11.0", "resolved": "11.11.0",
@ -289,11 +304,25 @@
"Microsoft.IdentityModel.Logging": "8.11.0" "Microsoft.IdentityModel.Logging": "8.11.0"
} }
}, },
"MimeKit": {
"type": "Transitive",
"resolved": "4.12.0",
"contentHash": "PFUHfs6BZxKYM/QPJksAwXphbJf0SEfdSfsoQ6p6yvFRaJPofFJMBiotWhFRrdSUzfp6C6K49EjBIqIwZ2TJqA==",
"dependencies": {
"BouncyCastle.Cryptography": "2.5.1",
"System.Security.Cryptography.Pkcs": "8.0.1"
}
},
"QuikGraph": { "QuikGraph": {
"type": "Transitive", "type": "Transitive",
"resolved": "2.5.0", "resolved": "2.5.0",
"contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw==" "contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw=="
}, },
"System.Formats.Asn1": {
"type": "Transitive",
"resolved": "8.0.1",
"contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A=="
},
"System.IdentityModel.Tokens.Jwt": { "System.IdentityModel.Tokens.Jwt": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.1", "resolved": "8.0.1",
@ -308,6 +337,11 @@
"resolved": "1.6.2", "resolved": "1.6.2",
"contentHash": "piIcdelf4dGotuIjFlyu7JLIZkYTmYM0ZTLGpCcxs9iCJFflhJht0nchkIV+GS5wfA3OtC3QNjIcUqyOdBdOsA==" "contentHash": "piIcdelf4dGotuIjFlyu7JLIZkYTmYM0ZTLGpCcxs9iCJFflhJht0nchkIV+GS5wfA3OtC3QNjIcUqyOdBdOsA=="
}, },
"System.Security.Cryptography.Pkcs": {
"type": "Transitive",
"resolved": "8.0.1",
"contentHash": "CoCRHFym33aUSf/NtWSVSZa99dkd0Hm7OCZUxORBjRB16LNhIEOf8THPqzIYlvKM0nNDAPTRBa1FxEECrgaxxA=="
},
"application": { "application": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {

View File

@ -65,6 +65,11 @@
"Microsoft.Extensions.Options": "8.0.0" "Microsoft.Extensions.Options": "8.0.0"
} }
}, },
"BouncyCastle.Cryptography": {
"type": "Transitive",
"resolved": "2.5.1",
"contentHash": "zy8TMeTP+1FH2NrLaNZtdRbBdq7u5MI+NFZQOBSM69u5RFkciinwzV2eveY6Kjf5MzgsYvvl6kTStsj3JrXqkg=="
},
"Castle.Core": { "Castle.Core": {
"type": "Transitive", "type": "Transitive",
"resolved": "5.1.1", "resolved": "5.1.1",
@ -87,6 +92,15 @@
"Microsoft.Extensions.Dependencyinjection.Abstractions": "2.1.0" "Microsoft.Extensions.Dependencyinjection.Abstractions": "2.1.0"
} }
}, },
"MailKit": {
"type": "Transitive",
"resolved": "4.12.1",
"contentHash": "rIqJm92qtHvk1hDchsJ95Hy7n46A7imE24ol++ikXBsjf3Bi1qDBu4H91FfY6LrYXJaxRlc2gIIpC8AOJrCbqg==",
"dependencies": {
"MimeKit": "4.12.0",
"System.Formats.Asn1": "8.0.1"
}
},
"MediatR": { "MediatR": {
"type": "Transitive", "type": "Transitive",
"resolved": "12.4.1", "resolved": "12.4.1",
@ -775,6 +789,15 @@
"System.Security.Principal.Windows": "4.5.0" "System.Security.Principal.Windows": "4.5.0"
} }
}, },
"MimeKit": {
"type": "Transitive",
"resolved": "4.12.0",
"contentHash": "PFUHfs6BZxKYM/QPJksAwXphbJf0SEfdSfsoQ6p6yvFRaJPofFJMBiotWhFRrdSUzfp6C6K49EjBIqIwZ2TJqA==",
"dependencies": {
"BouncyCastle.Cryptography": "2.5.1",
"System.Security.Cryptography.Pkcs": "8.0.1"
}
},
"Newtonsoft.Json": { "Newtonsoft.Json": {
"type": "Transitive", "type": "Transitive",
"resolved": "13.0.3", "resolved": "13.0.3",
@ -848,6 +871,11 @@
"resolved": "6.0.0", "resolved": "6.0.0",
"contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw=="
}, },
"System.Formats.Asn1": {
"type": "Transitive",
"resolved": "8.0.1",
"contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A=="
},
"System.IdentityModel.Tokens.Jwt": { "System.IdentityModel.Tokens.Jwt": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.1", "resolved": "8.0.1",
@ -1012,6 +1040,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Application": "[1.0.0, )", "Application": "[1.0.0, )",
"MailKit": "[4.12.1, )",
"Microsoft.Extensions.Http": "[9.0.4, )", "Microsoft.Extensions.Http": "[9.0.4, )",
"Newtonsoft.Json": "[13.0.3, )" "Newtonsoft.Json": "[13.0.3, )"
} }