using MimeKit; using MimeKit.Text; using Nop.Core.Domain.Media; using Nop.Core.Domain.Messages; using Nop.Core.Infrastructure; using Nop.Services.Media; namespace Nop.Services.Messages; /// /// Email sender /// public partial class EmailSender : IEmailSender { #region Fields protected readonly IDownloadService _downloadService; protected readonly INopFileProvider _fileProvider; protected readonly ISmtpBuilder _smtpBuilder; #endregion #region Ctor public EmailSender(IDownloadService downloadService, INopFileProvider fileProvider, ISmtpBuilder smtpBuilder) { _downloadService = downloadService; _fileProvider = fileProvider; _smtpBuilder = smtpBuilder; } #endregion #region Utilities /// /// Create an file attachment for the specific download object from DB /// /// Attachment download (another attachment) /// A leaf-node MIME part that contains an attachment. protected MimePart CreateMimeAttachment(Download download) { ArgumentNullException.ThrowIfNull(download); var fileName = !string.IsNullOrWhiteSpace(download.Filename) ? download.Filename : download.Id.ToString(); return CreateMimeAttachment($"{fileName}{download.Extension}", download.DownloadBinary, DateTime.UtcNow, DateTime.UtcNow, DateTime.UtcNow); } /// /// Create an file attachment for the specific file path /// /// Attachment file path /// Attachment file name /// /// A task that represents the asynchronous operation /// The task result contains a leaf-node MIME part that contains an attachment. /// protected virtual async Task CreateMimeAttachmentAsync(string filePath, string attachmentFileName = null) { ArgumentException.ThrowIfNullOrWhiteSpace(filePath); if (string.IsNullOrWhiteSpace(attachmentFileName)) attachmentFileName = Path.GetFileName(filePath); return CreateMimeAttachment( attachmentFileName, await _fileProvider.ReadAllBytesAsync(filePath), _fileProvider.GetCreationTime(filePath), _fileProvider.GetLastWriteTime(filePath), _fileProvider.GetLastAccessTime(filePath)); } /// /// Create an file attachment for the binary data /// /// Attachment file name /// The array of unsigned bytes from which to create the attachment stream. /// Creation date and time for the specified file or directory /// Date and time that the specified file or directory was last written to /// Date and time that the specified file or directory was last access to. /// A leaf-node MIME part that contains an attachment. protected MimePart CreateMimeAttachment(string attachmentFileName, byte[] binaryContent, DateTime cDate, DateTime mDate, DateTime rDate) { if (!ContentType.TryParse(MimeTypes.GetMimeType(attachmentFileName), out var mimeContentType)) mimeContentType = new ContentType("application", "octet-stream"); return new MimePart(mimeContentType) { FileName = attachmentFileName, Content = new MimeContent(new MemoryStream(binaryContent)), ContentDisposition = new ContentDisposition { CreationDate = cDate, ModificationDate = mDate, ReadDate = rDate }, ContentTransferEncoding = ContentEncoding.Base64 }; } #endregion #region Methods /// /// Sends an email /// /// Email account to use /// Subject /// Body /// From address /// From display name /// To address /// To display name /// ReplyTo address /// ReplyTo display name /// BCC addresses list /// CC addresses list /// Attachment file path /// Attachment file name. If specified, then this file name will be sent to a recipient. Otherwise, "AttachmentFilePath" name will be used. /// Attachment download ID (another attachment) /// Headers /// A task that represents the asynchronous operation public virtual async Task SendEmailAsync(EmailAccount emailAccount, string subject, string body, string fromAddress, string fromName, string toAddress, string toName, string replyTo = null, string replyToName = null, IEnumerable bcc = null, IEnumerable cc = null, string attachmentFilePath = null, string attachmentFileName = null, int attachedDownloadId = 0, IDictionary headers = null) { var message = new MimeMessage(); message.From.Add(new MailboxAddress(fromName, fromAddress)); message.To.Add(new MailboxAddress(toName, toAddress)); if (!string.IsNullOrEmpty(replyTo)) { message.ReplyTo.Add(new MailboxAddress(replyToName, replyTo)); } //BCC if (bcc != null) { foreach (var address in bcc.Where(bccValue => !string.IsNullOrWhiteSpace(bccValue))) { message.Bcc.Add(new MailboxAddress("", address.Trim())); } } //CC if (cc != null) { foreach (var address in cc.Where(ccValue => !string.IsNullOrWhiteSpace(ccValue))) { message.Cc.Add(new MailboxAddress("", address.Trim())); } } //content message.Subject = subject; //headers if (headers != null) foreach (var header in headers) { message.Headers.Add(header.Key, header.Value); } var multipart = new Multipart("mixed") { new TextPart(TextFormat.Html) { Text = body } }; //create the file attachment for this e-mail message if (!string.IsNullOrEmpty(attachmentFilePath) && _fileProvider.FileExists(attachmentFilePath)) { multipart.Add(await CreateMimeAttachmentAsync(attachmentFilePath, attachmentFileName)); } //another attachment? if (attachedDownloadId > 0) { var download = await _downloadService.GetDownloadByIdAsync(attachedDownloadId); //we do not support URLs as attachments if (!download?.UseDownloadUrl ?? false) { multipart.Add(CreateMimeAttachment(download)); } } message.Body = multipart; //send email using var smtpClient = await _smtpBuilder.BuildAsync(emailAccount); await smtpClient.SendAsync(message); await smtpClient.DisconnectAsync(true); } #endregion }