lukencode / FluentEmail

All in one email sender for .NET. Supports popular senders (SendGrid, MailGun, etc) and Razor templates.
https://lukelowrey.com/dotnet-email-guide-2021/
MIT License
3.04k stars 438 forks source link

File used in attachment is being locked after send #269

Open Gaz83 opened 3 years ago

Gaz83 commented 3 years ago

I have a program listening to messages from a Message Broker (Rabbit MQ) and when a message comes in it sends an email. In the message I have a property with attachment path, which is an ftp site. I check if this is valid and download the attachment. Once downloaded I attach to the email and send.

What I am noticing is that if I send the email, move on to the next message and try to repeat the above with the SAME attachment, I get the following error System.IO.IOException: The process cannot access the file 'C:\Temp\Somepic.jpg' because it is being used by another process.

This happens at the point when I try to delete the attachment file.

I am using the MailKit as the sender.

A bit of code to help

            public async Task Consume(ConsumeContext<EmailMessage> context)
    {
        await Task.Delay(5000); // Put this in case there needs to be a delay form the last email send to allow release of file.

        _logger.LogInformation("Email recieved");

        var message = context.Message;

        var _email = _emailFactory.Create(); // _emailFactory is injected into the class. I had IFluentEmail injected before, but tried this for a fresh email each time.

        _email.BCC(message.BccAddresses)
            .CC(message.CcAddresses)
            .To(message.ToAddresses)
            .Body(message.Body, message.IsHtml);

        if (message.HasAttachment)
        {
            GetAttachment(message, _email); // Exception happens in here when it tries to delete the file from the previous send.
        }

        _logger.LogInformation("Sending Email.");

        try
        {
            var response = await _email.SendAsync();

            if (response.Successful)
            {
                _logger.LogInformation("Sent");
            }
            else
            {
                foreach (var responseErrorMessage in response.ErrorMessages)
                {
                    _logger.LogInformation(responseErrorMessage);
                }
            }
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Error Sending email");
        }
    }

            private bool GetAttachment(EmailMessage message, IFluentEmail _email)
    {
        if (!string.IsNullOrEmpty(message.AttachmentPath))
        {
            _logger.LogInformation("Getting attachment");

            var pathSplit = message.AttachmentPath.Split(';');

            using var ftp = new FtpConnection(_logger, pathSplit[0], pathSplit[1], pathSplit[2]);

            if (ftp.OpenConnection())
            {
                var tempPath = @"D:\Temp\Services\Email";
                var fileName = Path.GetFileName(pathSplit[3]);
                var fullPath = Path.Combine(tempPath, fileName);

                if (File.Exists(fullPath))
                {
                    _logger.LogInformation("File exists in temp folder so deleting.");

                    try
                    {
                        File.Delete(fullPath); // IO Exception happens here
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                    }
                }

                _logger.LogInformation($"Downloading File {pathSplit[3]}.");

                ftp.DownloadFile(pathSplit[3], fullPath);

                if (File.Exists(fullPath))
                {
                    _email.AttachFromFilename(fullPath, GetContentTypeFromFileName(fullPath));
                }
                else
                {
                    _logger.LogError("Email could not be sent, file attachment did not download.");
                    return false;
                }
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }

        return true;
    }
CoderBrad commented 2 years ago

It looks like the issue is that AttachFromFilename opens a read stream which is never closed. I experienced the same issue with MailGun (which would make sense since the issue is in Core). My workaround is to call: email.Data.Attachments.ForEach(async f => await f.Data.DisposeAsync()); after SendAsync to close attachment streams so I can then do what I want with the files. Not optimal but I don't know what to suggest as a better option since I'm not sure how those streams are used throughout the library. My solution may even cause issues somewhere else but it seems to be working for me for now.