mirror of
https://github.com/bitwarden/server
synced 2026-01-07 02:53:38 +00:00
chore(docs): Add docs for legacy mail service
* Added docs for legacy mail service. * Updated namespaces. * Consolidated under Platform.Mail namespace * Updated obsolete comment. * Linting * Linting * Replaced documentation in original readme after accidental deletion.
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
using Bit.Core.Models.Mail;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public interface IMailDeliveryService
|
||||
{
|
||||
Task SendEmailAsync(MailMessage message);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Bit.Core.Models.Mail;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public interface IMailEnqueuingService
|
||||
{
|
||||
Task EnqueueAsync(IMailQueueMessage message, Func<IMailQueueMessage, Task> fallback);
|
||||
Task EnqueueManyAsync(IEnumerable<IMailQueueMessage> messages, Func<IMailQueueMessage, Task> fallback);
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Models.Mail;
|
||||
using Bit.Core.Vault.Models.Data;
|
||||
using Core.Auth.Enums;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public interface IMailService
|
||||
{
|
||||
Task SendWelcomeEmailAsync(User user);
|
||||
Task SendVerifyEmailEmailAsync(string email, Guid userId, string token);
|
||||
Task SendRegistrationVerificationEmailAsync(string email, string token);
|
||||
Task SendTrialInitiationSignupEmailAsync(
|
||||
bool isExistingUser,
|
||||
string email,
|
||||
string token,
|
||||
ProductTierType productTier,
|
||||
IEnumerable<ProductType> products,
|
||||
int trialLength);
|
||||
Task SendVerifyDeleteEmailAsync(string email, Guid userId, string token);
|
||||
Task SendCannotDeleteClaimedAccountEmailAsync(string email);
|
||||
Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail);
|
||||
Task SendChangeEmailEmailAsync(string newEmailAddress, string token);
|
||||
Task SendTwoFactorEmailAsync(string email, string accountEmail, string token, string deviceIp, string deviceType, TwoFactorEmailPurpose purpose);
|
||||
Task SendSendEmailOtpEmailAsync(string email, string token, string subject);
|
||||
/// <summary>
|
||||
/// <see cref="DefaultOtpTokenProviderOptions"/> has a default expiry of 5 minutes so we set the expiry to that value int he view model.
|
||||
/// Sends OTP code token to the specified email address.
|
||||
/// will replace <see cref="SendSendEmailOtpEmailAsync"/> when MJML templates are fully accepted.
|
||||
/// </summary>
|
||||
/// <param name="email">Email address to send the OTP to</param>
|
||||
/// <param name="token">Otp code token</param>
|
||||
/// <param name="subject">subject line of the email</param>
|
||||
/// <returns>Task</returns>
|
||||
Task SendSendEmailOtpEmailv2Async(string email, string token, string subject);
|
||||
Task SendFailedTwoFactorAttemptEmailAsync(string email, TwoFactorProviderType type, DateTime utcNow, string ip);
|
||||
Task SendNoMasterPasswordHintEmailAsync(string email);
|
||||
Task SendMasterPasswordHintEmailAsync(string email, string hint);
|
||||
|
||||
/// <summary>
|
||||
/// Sends one or many organization invite emails.
|
||||
/// </summary>
|
||||
/// <param name="orgInvitesInfo">The information required to send the organization invites.</param>
|
||||
Task SendOrganizationInviteEmailsAsync(OrganizationInvitesInfo orgInvitesInfo);
|
||||
Task SendOrganizationMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable<string> ownerEmails);
|
||||
Task SendOrganizationAutoscaledEmailAsync(Organization organization, int initialSeatCount, IEnumerable<string> ownerEmails);
|
||||
Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier, IEnumerable<string> adminEmails, bool hasAccessSecretsManager = false);
|
||||
Task SendOrganizationConfirmedEmailAsync(string organizationName, string email, bool hasAccessSecretsManager = false);
|
||||
Task SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(string organizationName, string email);
|
||||
Task SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(string organizationName, string email);
|
||||
Task SendPasswordlessSignInAsync(string returnUrl, string token, string email);
|
||||
Task SendInvoiceUpcoming(
|
||||
string email,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
bool mentionInvoices);
|
||||
Task SendInvoiceUpcoming(
|
||||
IEnumerable<string> email,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
bool mentionInvoices);
|
||||
Task SendProviderInvoiceUpcoming(
|
||||
IEnumerable<string> emails,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
string? collectionMethod,
|
||||
bool hasPaymentMethod,
|
||||
string? paymentMethodDescription);
|
||||
Task SendPaymentFailedAsync(string email, decimal amount, bool mentionInvoices);
|
||||
Task SendAddedCreditAsync(string email, decimal amount);
|
||||
Task SendLicenseExpiredAsync(IEnumerable<string> emails, string? organizationName = null);
|
||||
Task SendNewDeviceLoggedInEmail(string email, string deviceType, DateTime timestamp, string ip);
|
||||
Task SendRecoverTwoFactorEmail(string email, DateTime timestamp, string ip);
|
||||
Task SendEmergencyAccessInviteEmailAsync(EmergencyAccess emergencyAccess, string name, string token);
|
||||
Task SendEmergencyAccessAcceptedEmailAsync(string granteeEmail, string email);
|
||||
Task SendEmergencyAccessConfirmedEmailAsync(string grantorName, string email);
|
||||
Task SendEmergencyAccessRecoveryInitiated(EmergencyAccess emergencyAccess, string initiatingName, string email);
|
||||
Task SendEmergencyAccessRecoveryApproved(EmergencyAccess emergencyAccess, string approvingName, string email);
|
||||
Task SendEmergencyAccessRecoveryRejected(EmergencyAccess emergencyAccess, string rejectingName, string email);
|
||||
Task SendEmergencyAccessRecoveryReminder(EmergencyAccess emergencyAccess, string initiatingName, string email);
|
||||
Task SendEmergencyAccessRecoveryTimedOut(EmergencyAccess ea, string initiatingName, string email);
|
||||
Task SendEnqueuedMailMessageAsync(IMailQueueMessage queueMessage);
|
||||
Task SendAdminResetPasswordEmailAsync(string email, string? userName, string orgName);
|
||||
Task SendProviderSetupInviteEmailAsync(Provider provider, string token, string email);
|
||||
Task SendBusinessUnitConversionInviteAsync(Organization organization, string token, string email);
|
||||
Task SendProviderInviteEmailAsync(string providerName, ProviderUser providerUser, string token, string email);
|
||||
Task SendProviderConfirmedEmailAsync(string providerName, string email);
|
||||
Task SendProviderUserRemoved(string providerName, string email);
|
||||
Task SendProviderUpdatePaymentMethod(
|
||||
Guid organizationId,
|
||||
string organizationName,
|
||||
string providerName,
|
||||
IEnumerable<string> emails);
|
||||
Task SendUpdatedTempPasswordEmailAsync(string email, string userName);
|
||||
Task SendFamiliesForEnterpriseOfferEmailAsync(string sponsorOrgName, string email, bool existingAccount, string token);
|
||||
Task BulkSendFamiliesForEnterpriseOfferEmailAsync(string SponsorOrgName, IEnumerable<(string Email, bool ExistingAccount, string Token)> invites);
|
||||
Task SendFamiliesForEnterpriseRedeemedEmailsAsync(string familyUserEmail, string sponsorEmail);
|
||||
Task SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(string email, DateTime expirationDate);
|
||||
Task SendOTPEmailAsync(string email, string token);
|
||||
Task SendUnclaimedOrganizationDomainEmailAsync(IEnumerable<string> adminEmails, string organizationId, string domainName);
|
||||
Task SendSecretsManagerMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable<string> ownerEmails);
|
||||
Task SendSecretsManagerMaxServiceAccountLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable<string> ownerEmails);
|
||||
Task SendTrustedDeviceAdminApprovalEmailAsync(string email, DateTime utcNow, string ip, string deviceTypeAndIdentifier);
|
||||
Task SendTrialInitiationEmailAsync(string email);
|
||||
Task SendInitiateDeletProviderEmailAsync(string email, Provider provider, string token);
|
||||
Task SendInitiateDeleteOrganzationEmailAsync(string email, Organization organization, string token);
|
||||
Task SendRequestSMAccessToAdminEmailAsync(IEnumerable<string> adminEmails, string organizationName, string userRequestingAccess, string emailContent);
|
||||
#nullable disable
|
||||
Task SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(string email, string offerAcceptanceDate, string organizationId,
|
||||
string organizationName);
|
||||
#nullable enable
|
||||
Task SendClaimedDomainUserEmailAsync(ClaimedUserDomainClaimedEmails emailList);
|
||||
Task SendDeviceApprovalRequestedNotificationEmailAsync(IEnumerable<string> adminEmails, Guid organizationId, string email, string? userName);
|
||||
Task SendBulkSecurityTaskNotificationsAsync(Organization org, IEnumerable<UserSecurityTasksCount> securityTaskNotifications, IEnumerable<string> adminOwnerEmails);
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using Amazon;
|
||||
using Amazon.SimpleEmail;
|
||||
using Amazon.SimpleEmail.Model;
|
||||
using Bit.Core.Models.Mail;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class AmazonSesMailDeliveryService : IMailDeliveryService, IDisposable
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IWebHostEnvironment _hostingEnvironment;
|
||||
private readonly ILogger<AmazonSesMailDeliveryService> _logger;
|
||||
private readonly IAmazonSimpleEmailService _client;
|
||||
private readonly string _source;
|
||||
private readonly string _senderTag;
|
||||
private readonly string? _configSetName;
|
||||
|
||||
public AmazonSesMailDeliveryService(
|
||||
GlobalSettings globalSettings,
|
||||
IWebHostEnvironment hostingEnvironment,
|
||||
ILogger<AmazonSesMailDeliveryService> logger)
|
||||
: this(globalSettings, hostingEnvironment, logger,
|
||||
new AmazonSimpleEmailServiceClient(
|
||||
globalSettings.Amazon.AccessKeyId,
|
||||
globalSettings.Amazon.AccessKeySecret,
|
||||
RegionEndpoint.GetBySystemName(globalSettings.Amazon.Region))
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public AmazonSesMailDeliveryService(
|
||||
GlobalSettings globalSettings,
|
||||
IWebHostEnvironment hostingEnvironment,
|
||||
ILogger<AmazonSesMailDeliveryService> logger,
|
||||
IAmazonSimpleEmailService amazonSimpleEmailService)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(globalSettings.Amazon?.AccessKeyId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(globalSettings.Amazon.AccessKeyId));
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(globalSettings.Amazon?.AccessKeySecret))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(globalSettings.Amazon.AccessKeySecret));
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(globalSettings.Amazon?.Region))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(globalSettings.Amazon.Region));
|
||||
}
|
||||
|
||||
var replyToEmail = CoreHelpers.PunyEncode(globalSettings.Mail.ReplyToEmail);
|
||||
|
||||
_globalSettings = globalSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_logger = logger;
|
||||
_client = amazonSimpleEmailService;
|
||||
_source = $"\"{globalSettings.SiteName}\" <{replyToEmail}>";
|
||||
_senderTag = $"Server_{globalSettings.ProjectName?.Replace(' ', '_')}";
|
||||
if (!string.IsNullOrWhiteSpace(_globalSettings.Mail.AmazonConfigSetName))
|
||||
{
|
||||
_configSetName = _globalSettings.Mail.AmazonConfigSetName;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client?.Dispose();
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(MailMessage message)
|
||||
{
|
||||
var request = new SendEmailRequest
|
||||
{
|
||||
ConfigurationSetName = _configSetName,
|
||||
Source = _source,
|
||||
Destination = new Destination
|
||||
{
|
||||
ToAddresses = message.ToEmails
|
||||
.Select(email => CoreHelpers.PunyEncode(email))
|
||||
.ToList()
|
||||
},
|
||||
Message = new Message
|
||||
{
|
||||
Subject = new Content(message.Subject),
|
||||
Body = new Body
|
||||
{
|
||||
Html = new Content
|
||||
{
|
||||
Charset = "UTF-8",
|
||||
Data = message.HtmlContent
|
||||
},
|
||||
Text = new Content
|
||||
{
|
||||
Charset = "UTF-8",
|
||||
Data = message.TextContent
|
||||
}
|
||||
}
|
||||
},
|
||||
Tags = new List<MessageTag>
|
||||
{
|
||||
new MessageTag { Name = "Environment", Value = _hostingEnvironment.EnvironmentName },
|
||||
new MessageTag { Name = "Sender", Value = _senderTag }
|
||||
}
|
||||
};
|
||||
|
||||
if (message.BccEmails?.Any() ?? false)
|
||||
{
|
||||
request.Destination.BccAddresses = message.BccEmails
|
||||
.Select(email => CoreHelpers.PunyEncode(email))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(message.Category))
|
||||
{
|
||||
request.Tags.Add(new MessageTag { Name = "Category", Value = message.Category });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await SendAsync(request, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Failed to send email. Retrying...");
|
||||
await SendAsync(request, true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendAsync(SendEmailRequest request, bool retry)
|
||||
{
|
||||
if (retry)
|
||||
{
|
||||
// wait and try again
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
await _client.SendEmailAsync(request);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Azure.Storage.Queues;
|
||||
using Bit.Core.Models.Mail;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class AzureQueueMailService : AzureQueueService<IMailQueueMessage>, IMailEnqueuingService
|
||||
{
|
||||
public AzureQueueMailService(GlobalSettings globalSettings) : base(
|
||||
new QueueClient(globalSettings.Mail.ConnectionString, "mail"),
|
||||
JsonHelpers.IgnoreWritingNull)
|
||||
{ }
|
||||
|
||||
public Task EnqueueAsync(IMailQueueMessage message, Func<IMailQueueMessage, Task> fallback) =>
|
||||
CreateManyAsync(new[] { message });
|
||||
|
||||
public Task EnqueueManyAsync(IEnumerable<IMailQueueMessage> messages, Func<IMailQueueMessage, Task> fallback) =>
|
||||
CreateManyAsync(messages);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Bit.Core.Models.Mail;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class BlockingMailEnqueuingService : IMailEnqueuingService
|
||||
{
|
||||
public async Task EnqueueAsync(IMailQueueMessage message, Func<IMailQueueMessage, Task> fallback)
|
||||
{
|
||||
await fallback(message);
|
||||
}
|
||||
|
||||
public async Task EnqueueManyAsync(IEnumerable<IMailQueueMessage> messages, Func<IMailQueueMessage, Task> fallback)
|
||||
{
|
||||
foreach (var message in messages)
|
||||
{
|
||||
await fallback(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,123 +0,0 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using MailKit.Net.Smtp;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MimeKit;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class MailKitSmtpMailDeliveryService : IMailDeliveryService
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly ILogger<MailKitSmtpMailDeliveryService> _logger;
|
||||
private readonly string _replyDomain;
|
||||
private readonly string _replyEmail;
|
||||
|
||||
public MailKitSmtpMailDeliveryService(
|
||||
GlobalSettings globalSettings,
|
||||
ILogger<MailKitSmtpMailDeliveryService> logger)
|
||||
{
|
||||
if (globalSettings.Mail.Smtp?.Host == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(globalSettings.Mail.Smtp.Host));
|
||||
}
|
||||
|
||||
if (globalSettings.Mail.ReplyToEmail == null)
|
||||
{
|
||||
throw new InvalidOperationException("A GlobalSettings.Mail.ReplyToEmail is required to be set up.");
|
||||
}
|
||||
|
||||
_replyEmail = CoreHelpers.PunyEncode(globalSettings.Mail.ReplyToEmail);
|
||||
|
||||
if (_replyEmail.Contains("@"))
|
||||
{
|
||||
_replyDomain = _replyEmail.Split('@')[1];
|
||||
}
|
||||
|
||||
_globalSettings = globalSettings;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(Models.Mail.MailMessage message)
|
||||
=> await SendEmailAsync(message, CancellationToken.None);
|
||||
|
||||
public async Task SendEmailAsync(Models.Mail.MailMessage message, CancellationToken cancellationToken)
|
||||
{
|
||||
var mimeMessage = new MimeMessage();
|
||||
mimeMessage.From.Add(new MailboxAddress(_globalSettings.SiteName, _replyEmail));
|
||||
mimeMessage.Subject = message.Subject;
|
||||
if (!string.IsNullOrWhiteSpace(_replyDomain))
|
||||
{
|
||||
mimeMessage.MessageId = $"<{Guid.NewGuid()}@{_replyDomain}>";
|
||||
}
|
||||
|
||||
foreach (var address in message.ToEmails)
|
||||
{
|
||||
var punyencoded = CoreHelpers.PunyEncode(address);
|
||||
mimeMessage.To.Add(MailboxAddress.Parse(punyencoded));
|
||||
}
|
||||
|
||||
if (message.BccEmails != null)
|
||||
{
|
||||
foreach (var address in message.BccEmails)
|
||||
{
|
||||
var punyencoded = CoreHelpers.PunyEncode(address);
|
||||
mimeMessage.Bcc.Add(MailboxAddress.Parse(punyencoded));
|
||||
}
|
||||
}
|
||||
|
||||
var builder = new BodyBuilder();
|
||||
if (!string.IsNullOrWhiteSpace(message.TextContent))
|
||||
{
|
||||
builder.TextBody = message.TextContent;
|
||||
}
|
||||
builder.HtmlBody = message.HtmlContent;
|
||||
mimeMessage.Body = builder.ToMessageBody();
|
||||
|
||||
using (var client = new SmtpClient())
|
||||
{
|
||||
if (_globalSettings.Mail.Smtp.TrustServer)
|
||||
{
|
||||
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
}
|
||||
|
||||
if (!_globalSettings.Mail.Smtp.StartTls && !_globalSettings.Mail.Smtp.Ssl &&
|
||||
_globalSettings.Mail.Smtp.Port == 25)
|
||||
{
|
||||
await client.ConnectAsync(
|
||||
_globalSettings.Mail.Smtp.Host,
|
||||
_globalSettings.Mail.Smtp.Port,
|
||||
MailKit.Security.SecureSocketOptions.None,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
var useSsl = _globalSettings.Mail.Smtp.Port == 587 && !_globalSettings.Mail.Smtp.SslOverride ?
|
||||
false : _globalSettings.Mail.Smtp.Ssl;
|
||||
await client.ConnectAsync(
|
||||
_globalSettings.Mail.Smtp.Host,
|
||||
_globalSettings.Mail.Smtp.Port,
|
||||
useSsl,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
if (CoreHelpers.SettingHasValue(_globalSettings.Mail.Smtp.Username) &&
|
||||
CoreHelpers.SettingHasValue(_globalSettings.Mail.Smtp.Password))
|
||||
{
|
||||
await client.AuthenticateAsync(
|
||||
_globalSettings.Mail.Smtp.Username,
|
||||
_globalSettings.Mail.Smtp.Password,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
await client.SendAsync(mimeMessage, cancellationToken);
|
||||
await client.DisconnectAsync(true, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Bit.Core.Models.Mail;
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class MultiServiceMailDeliveryService : IMailDeliveryService
|
||||
{
|
||||
private readonly IMailDeliveryService _sesService;
|
||||
private readonly IMailDeliveryService _sendGridService;
|
||||
private readonly int _sendGridPercentage;
|
||||
|
||||
private static Random _random = new Random();
|
||||
|
||||
public MultiServiceMailDeliveryService(
|
||||
GlobalSettings globalSettings,
|
||||
IWebHostEnvironment hostingEnvironment,
|
||||
ILogger<AmazonSesMailDeliveryService> sesLogger,
|
||||
ILogger<SendGridMailDeliveryService> sendGridLogger)
|
||||
{
|
||||
_sesService = new AmazonSesMailDeliveryService(globalSettings, hostingEnvironment, sesLogger);
|
||||
_sendGridService = new SendGridMailDeliveryService(globalSettings, hostingEnvironment, sendGridLogger);
|
||||
|
||||
// disabled by default (-1)
|
||||
_sendGridPercentage = (globalSettings.Mail?.SendGridPercentage).GetValueOrDefault(-1);
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(MailMessage message)
|
||||
{
|
||||
var roll = _random.Next(0, 99);
|
||||
if (roll < _sendGridPercentage)
|
||||
{
|
||||
await _sendGridService.SendEmailAsync(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _sesService.SendEmailAsync(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using Bit.Core.Models.Mail;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SendGrid;
|
||||
using SendGrid.Helpers.Mail;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class SendGridMailDeliveryService : IMailDeliveryService, IDisposable
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IWebHostEnvironment _hostingEnvironment;
|
||||
private readonly ILogger<SendGridMailDeliveryService> _logger;
|
||||
private readonly ISendGridClient _client;
|
||||
private readonly string _senderTag;
|
||||
private readonly string _replyToEmail;
|
||||
|
||||
public SendGridMailDeliveryService(
|
||||
GlobalSettings globalSettings,
|
||||
IWebHostEnvironment hostingEnvironment,
|
||||
ILogger<SendGridMailDeliveryService> logger)
|
||||
: this(new SendGridClient(globalSettings.Mail.SendGridApiKey, globalSettings.Mail.SendGridApiHost),
|
||||
globalSettings, hostingEnvironment, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// TODO: nothing to dispose
|
||||
}
|
||||
|
||||
public SendGridMailDeliveryService(
|
||||
ISendGridClient client,
|
||||
GlobalSettings globalSettings,
|
||||
IWebHostEnvironment hostingEnvironment,
|
||||
ILogger<SendGridMailDeliveryService> logger)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(globalSettings.Mail?.SendGridApiKey))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(globalSettings.Mail.SendGridApiKey));
|
||||
}
|
||||
|
||||
_globalSettings = globalSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_logger = logger;
|
||||
_client = client;
|
||||
_senderTag = $"Server_{globalSettings.ProjectName?.Replace(' ', '_')}";
|
||||
_replyToEmail = CoreHelpers.PunyEncode(globalSettings.Mail.ReplyToEmail);
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(MailMessage message)
|
||||
{
|
||||
var msg = new SendGridMessage();
|
||||
msg.SetFrom(new EmailAddress(_replyToEmail, _globalSettings.SiteName));
|
||||
msg.AddTos(message.ToEmails.Select(e => new EmailAddress(CoreHelpers.PunyEncode(e))).ToList());
|
||||
if (message.BccEmails?.Any() ?? false)
|
||||
{
|
||||
msg.AddBccs(message.BccEmails.Select(e => new EmailAddress(CoreHelpers.PunyEncode(e))).ToList());
|
||||
}
|
||||
|
||||
msg.SetSubject(message.Subject);
|
||||
msg.AddContent(MimeType.Text, message.TextContent);
|
||||
msg.AddContent(MimeType.Html, message.HtmlContent);
|
||||
|
||||
msg.AddCategory($"type:{message.Category}");
|
||||
msg.AddCategory($"env:{_hostingEnvironment.EnvironmentName}");
|
||||
msg.AddCategory($"sender:{_senderTag}");
|
||||
|
||||
msg.SetClickTracking(false, false);
|
||||
msg.SetOpenTracking(false);
|
||||
|
||||
if (message.MetaData != null &&
|
||||
message.MetaData.TryGetValue("SendGridBypassListManagement", out var sendGridBypassListManagement) &&
|
||||
Convert.ToBoolean(sendGridBypassListManagement))
|
||||
{
|
||||
msg.SetBypassListManagement(true);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var success = await SendAsync(msg, false);
|
||||
if (!success)
|
||||
{
|
||||
_logger.LogWarning("Failed to send email. Retrying...");
|
||||
await SendAsync(msg, true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Failed to send email (with exception). Retrying...");
|
||||
await SendAsync(msg, true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> SendAsync(SendGridMessage message, bool retry)
|
||||
{
|
||||
if (retry)
|
||||
{
|
||||
// wait and try again
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
|
||||
var response = await _client.SendEmailAsync(message);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var responseBody = await response.Body.ReadAsStringAsync();
|
||||
_logger.LogError("SendGrid email sending failed with {0}: {1}", response.StatusCode, responseBody);
|
||||
}
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Bit.Core.Models.Mail;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class NoopMailDeliveryService : IMailDeliveryService
|
||||
{
|
||||
public Task SendEmailAsync(MailMessage message)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Models.Mail;
|
||||
using Bit.Core.Vault.Models.Data;
|
||||
using Core.Auth.Enums;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class NoopMailService : IMailService
|
||||
{
|
||||
public Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendVerifyEmailEmailAsync(string email, Guid userId, string hint)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendRegistrationVerificationEmailAsync(string email, string hint)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendTrialInitiationSignupEmailAsync(
|
||||
bool isExistingUser,
|
||||
string email,
|
||||
string token,
|
||||
ProductTierType productTier,
|
||||
IEnumerable<ProductType> products,
|
||||
int trailLength)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendChangeEmailEmailAsync(string newEmailAddress, string token)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendMasterPasswordHintEmailAsync(string email, string hint)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendNoMasterPasswordHintEmailAsync(string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendOrganizationMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable<string> ownerEmails)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendOrganizationAutoscaledEmailAsync(Organization organization, int initialSeatCount, IEnumerable<string> ownerEmails)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier,
|
||||
IEnumerable<string> adminEmails, bool hasAccessSecretsManager = false)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendOrganizationConfirmedEmailAsync(string organizationName, string email, bool hasAccessSecretsManager = false)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendOrganizationInviteEmailsAsync(OrganizationInvitesInfo orgInvitesInfo)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(string organizationName, string email) =>
|
||||
Task.CompletedTask;
|
||||
|
||||
public Task SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(string organizationName, string email) =>
|
||||
Task.CompletedTask;
|
||||
|
||||
public Task SendTwoFactorEmailAsync(string email, string accountEmail, string token, string deviceIp, string deviceType, TwoFactorEmailPurpose purpose)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendSendEmailOtpEmailAsync(string email, string token, string subject)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendSendEmailOtpEmailv2Async(string email, string token, string subject)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendFailedTwoFactorAttemptEmailAsync(string email, TwoFactorProviderType failedType, DateTime utcNow, string ip)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendWelcomeEmailAsync(User user)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendVerifyDeleteEmailAsync(string email, Guid userId, string token)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendCannotDeleteClaimedAccountEmailAsync(string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendPasswordlessSignInAsync(string returnUrl, string token, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendInvoiceUpcoming(
|
||||
string email,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
bool mentionInvoices) => Task.FromResult(0);
|
||||
|
||||
public Task SendInvoiceUpcoming(
|
||||
IEnumerable<string> emails,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
bool mentionInvoices) => Task.FromResult(0);
|
||||
|
||||
public Task SendProviderInvoiceUpcoming(
|
||||
IEnumerable<string> emails,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
string? collectionMethod = null,
|
||||
bool hasPaymentMethod = true,
|
||||
string? paymentMethodDescription = null) => Task.FromResult(0);
|
||||
|
||||
public Task SendPaymentFailedAsync(string email, decimal amount, bool mentionInvoices)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendAddedCreditAsync(string email, decimal amount)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendLicenseExpiredAsync(IEnumerable<string> emails, string? organizationName = null)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendNewDeviceLoggedInEmail(string email, string deviceType, DateTime timestamp, string ip)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendRecoverTwoFactorEmail(string email, DateTime timestamp, string ip)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendEmergencyAccessInviteEmailAsync(EmergencyAccess emergencyAccess, string name, string token)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendEmergencyAccessAcceptedEmailAsync(string granteeEmail, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendEmergencyAccessConfirmedEmailAsync(string grantorName, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendEmergencyAccessRecoveryInitiated(EmergencyAccess emergencyAccess, string initiatingName, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendEmergencyAccessRecoveryApproved(EmergencyAccess emergencyAccess, string approvingName, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendEmergencyAccessRecoveryRejected(EmergencyAccess emergencyAccess, string rejectingName, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendEmergencyAccessRecoveryReminder(EmergencyAccess emergencyAccess, string initiatingName, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendEmergencyAccessRecoveryTimedOut(EmergencyAccess ea, string initiatingName, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendEnqueuedMailMessageAsync(IMailQueueMessage queueMessage)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendAdminResetPasswordEmailAsync(string email, string? userName, string orgName)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendProviderSetupInviteEmailAsync(Provider provider, string token, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendBusinessUnitConversionInviteAsync(Organization organization, string token, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendProviderInviteEmailAsync(string providerName, ProviderUser providerUser, string token, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendProviderConfirmedEmailAsync(string providerName, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendProviderUserRemoved(string providerName, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendProviderUpdatePaymentMethod(Guid organizationId, string organizationName, string providerName,
|
||||
IEnumerable<string> emails) => Task.FromResult(0);
|
||||
|
||||
public Task SendUpdatedTempPasswordEmailAsync(string email, string userName)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendFamiliesForEnterpriseOfferEmailAsync(string SponsorOrgName, string email, bool existingAccount, string token)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task BulkSendFamiliesForEnterpriseOfferEmailAsync(string SponsorOrgName, IEnumerable<(string Email, bool ExistingAccount, string Token)> invites)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendFamiliesForEnterpriseRedeemedEmailsAsync(string familyUserEmail, string sponsorEmail)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(string email, DateTime expirationDate)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendOTPEmailAsync(string email, string token)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendUnclaimedOrganizationDomainEmailAsync(IEnumerable<string> adminEmails, string organizationId, string domainName)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendSecretsManagerMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount,
|
||||
IEnumerable<string> ownerEmails)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendSecretsManagerMaxServiceAccountLimitReachedEmailAsync(Organization organization,
|
||||
int maxSeatCount,
|
||||
IEnumerable<string> ownerEmails)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendTrustedDeviceAdminApprovalEmailAsync(string email, DateTime utcNow, string ip, string deviceTypeAndIdentifier)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendTrialInitiationEmailAsync(string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendInitiateDeletProviderEmailAsync(string email, Provider provider, string token) => throw new NotImplementedException();
|
||||
|
||||
public Task SendInitiateDeleteOrganzationEmailAsync(string email, Organization organization, string token)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
public Task SendRequestSMAccessToAdminEmailAsync(IEnumerable<string> adminEmails, string organizationName, string userRequestingAccess, string emailContent) => throw new NotImplementedException();
|
||||
|
||||
public Task SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(string email, string offerAcceptanceDate,
|
||||
string organizationId,
|
||||
string organizationName)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
public Task SendClaimedDomainUserEmailAsync(ClaimedUserDomainClaimedEmails emailList) => Task.CompletedTask;
|
||||
|
||||
public Task SendDeviceApprovalRequestedNotificationEmailAsync(IEnumerable<string> adminEmails, Guid organizationId, string email, string? userName)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendBulkSecurityTaskNotificationsAsync(Organization org, IEnumerable<UserSecurityTasksCount> securityTaskNotifications, IEnumerable<string> adminOwnerEmails)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user