Using SmtpClient.Send with attachment locks file, even after implementing Dispose() - c#

This is my first time asking a question on StackOverflow, so let me know if you need more clarification. I am attempting to send emails with an attachment to users of a system, however I am having difficulty cleaning up the attachments on the file system after the messages are sent.
Background
I am using SSIS to compile a listing of email recipients and message content from a SQL database, then using a Script Component to do the actual email sending with C#. I am more of a DBA/Project Manager by role, and know enough coding to do some small things, but it has been quite some years since I did any .Net coding on a daily basis.
Each time I run the routine, the attachment files (Excel files in this case) are created successfully in a dedicated directory, then the emails are generated using those attachments and placed in a pickup folder. As each email is created and sent, I want to delete the attachment from its directory, but I get this error:
The process cannot access the file 'D:\mailtest\{filename}.xls' because it is being used by another process.
The specific file that it errors on is different each time, and of the ~3000 emails that are to be generated, it fails at around the 1200-1500 emails mark.
Code
//Create message
using (MailMessage msg = new MailMessage())
{
msg.To.Add(new MailAddress(EmailRecipient));
if (Row.Cc.Length > 0)
{
msg.CC.Add(new MailAddress(Row.Cc));
}
if (Row.Bcc.Length > 0)
{
msg.Bcc.Add(new MailAddress(Row.Bcc));
}
msg.From = new MailAddress(EmailSender);
msg.Subject = MessageSubject;
msg.Body = MessageBody +
"\n" +
"\n" +
this.Variables.EmailFooter;
msg.IsBodyHtml = true;
msg.BodyEncoding = System.Text.Encoding.UTF8;
//Add attachment data
if (File.Exists(attachmentPath))
{
Attachment data = new Attachment(attachmentPath);
data.ContentDisposition.FileName = "Expiring Training.xls";
msg.Attachments.Add(data);
}
if (this.Variables.UsePickupDirectory) //Drops items into pickup directory
{
SmtpClient client = new SmtpClient(SMTPEndPoint, SMTPPort)
{
EnableSsl = false,
DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory,
PickupDirectoryLocation = this.Variables.PickupDirectory,
Credentials = new NetworkCredential(UserName, Password)
};
try
{
client.Send(msg);
}
catch (Exception e)
{
//Debug.WriteLine(e);
Row.ErrorMessage = e.Message;
}
finally
{
msg.Dispose(); //Release file
}
}
else //Send to SMTP Server
{
SmtpClient client = new SmtpClient(SMTPEndPoint, SMTPPort)
{
EnableSsl = true,
DeliveryMethod = SmtpDeliveryMethod.Network,
Credentials = new NetworkCredential(UserName, Password)
};
try
{
client.Send(msg);
}
catch (Exception e)
{
//Debug.WriteLine(e);
Row.ErrorMessage = e.Message;
}
finally
{
//Add sleep to prevent sending more than 10 emails per second
System.Threading.Thread.Sleep(100);
msg.Dispose(); //Release file
}
}
}
//Remove attachment file
if (File.Exists(attachmentPath))
{
File.Delete(attachmentPath);
}
Things I've Tried
Initially, I did not have the using block. I saw this suggestion on a similar SO question, but adding it here seems to make no difference.
I've added the Dispose() to the finally block of each path (only the pickup directory is used in this case), which I understand should release all locks on the files used as attachments.
Without either of those things, it fails on the first file encountered, which leads me to believe it is working, but only for a while, and then fails suddenly at a random point during the execution.
The file specified in the error is not showing in process explorer when I search for it, so maybe it is released quickly after the error, so that I cannot search in time?
I tried moving the "Delete" functionality to a separate process entirely, running directly after all emails had been sent, but the same message would appear anyway.
I'd be thankful for any advice if anyone has a clue what could be happening.
New info
Adding some extra error handling did improve things, but didn't totally fix it. I've had several runs make it all the way through successfully, and whenever there was an error, it was only on 1 out of the ~3000 files. It did show the same error, though: "The process cannot access the file 'D:\mailtest{...}.xls' because it is being used by another process." That error was from the File.Delete line. It makes me wonder how just by adding more stuff it errors less. Is the sending and deleting happening so fast that it's stepping on its own toes? And throwing stuff in slows it down enough that it's able to keep up?
New Info 2
I took Jamal's advice and added a 500ms delay between send and delete, but only if the first delete attempt failed. So far no errors on 10 straight runs, whereas it was failing every single run in some way prior to adding it. However, the FireInformation message never appeared in the output window, leading me to think it never reached that block, so I'm not sure why adding it seems to work.
//Remove attachment file
if (File.Exists(attachmentPath))
{
try
{
File.Delete(attachmentPath);
}
catch
{
try
{
this.ComponentMetaData.FireInformation(0, "Delete failed", "Trying Delete again after 500ms", "", 0, fireAgain);
System.Threading.Thread.Sleep(500);
File.Delete(attachmentPath);
}
catch (Exception e)
{
Row.ErrorMessage = e.Message;
this.ComponentMetaData.FireInformation(e.HResult, e.Source, e.Message, e.HelpLink, 0, fireAgain);
}
}
}

Related

DeleteMode.HardDelete not working as expected

I currently have a process that is designed to iterate through an Inbox, parse the emails contained within, dump those to a JSON file, and delete the emails. They cannot go to the DeletedItems folder, and must be removed from the server immediately (so DeleteMode.HardDelete is the only option here).
There is an admin monitoring the Inbox to make sure the process is truly deleting the emails, and had alerted me this morning that the RecoverableItems folder is getting filled up. I had consulted this to see if I had done something wrong. Additionally, I had made sure to look over Delete Modes with the EWS API.
Now, the pertinent piece of code I have is the following:
Boolean moreItems = true;
int loopNumber = 0;
while (moreItems)
{
string filepath = "Outputs/";
string filename = "some_filename.json";
using (StreamWriter sw = new StreamWriter(filepath + filename))
{
int itemsParsed = 0;
while (moreItems && itemsParsed < 50000)
// I want to split up the files into 50k item files.
{
try
{
FindItemsResults<Item> itemSet = i.GetItemSet();
foreach (Item x in itemSet.Items)
{
//Console.WriteLine($"Getting mailitem {x.Id}");
try
{
x.Load(itemSetProperties);
EmailMessage email = em.BindEmail(service , x.Id);
metadata.Id = x.Id.ToString();
metadata.Subject = x.Subject.ToString();
metadata.Sender = email.From.Address.ToString();
metadata.Body = x.Body.ToString();
metadata.DateTimeReceived = x.DateTimeReceived;
metadata.DateTimeSent = x.DateTimeSent;
metadata.HasAttachments = email.HasAttachments;
serializer.Serialize(sw , metadata);
moreItems = itemSet.MoreAvailable;
itemsParsed += 1;
x.Delete(DeleteMode.HardDelete);
}
catch (ServiceRequestException sre)
{
Console.WriteLine("\nTimeout caught.\n");
//Connection keeps getting reset every once in a while
}
catch (XmlException xle)
{
x.Delete(DeleteMode.HardDelete);
// There are emails with incomplete xml in the body. These can be deleted
}
catch(Exception ex)
{
Console.WriteLine($"Caught new exception: \n{ex.Message}");
Environment.Exit(0);
// I want to catch new exceptions and figure out why they're happening
}
Console.WriteLine($"Parsed {itemsParsed} items");
}
}
catch (ServiceRequestException exterior_sre)
{
Console.WriteLine("Caught exterior exception");
// Still haven't figured this out yet, but it occurs once
// in a rare while, and continuing does not affect the overall process
}
}
}
loopNumber += 1;
Console.WriteLine($"Moving to loop number {loopNumber}");
}
The issue is that the items being deleted are still being sent to the RecoverableItems folder in the dumpster. In the interrim, I had asked that the admin see about altering the retention policy on RecoverableItems such that it would get cleared out sooner than the default.
My main question is why this is happening? The emails appear to be getting a SoftDelete, so I know that the delete call to EWS is occurring successfully, but the HardDelete seems to be getting ignored.
I am still relatively inexperienced with the EWS API and C# as a whole, so apologies in advance for any ignorance on my part.
Update
I have confirmed with the Admin that items are not landing in the "Purges" folder, where they should be landing. I also have checked with this piece of documentation, that outlines the need for setting up retention policies on the Recoverable Items folder.
I'll keep adding edits for this as I go.
I believe it is doing as it is designed with Hard Delete -
Hard delete
The item or folder is permanently deleted.
Hard-deleted
items are placed in the Purges folder of the dumpster. This is like
when the recycling truck empties your curbside recycle container. The
items cannot be accessed from an email client like Outlook or Outlook
Web App, and, unless there is a hold set on the mailbox, the items
will be permanently deleted after a set period of time.
You can read
more about item retention in the article Configure Deleted Item
Retention and Recoverable Items Quotas.
NOTE: Folders are not placed in the Purges folder when they are hard
deleted. Hard-deleted folders are removed from the mailbox.
https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/deleting-items-by-using-ews-in-exchange

SmtpException: Failure sending mail. Loop sending with Attachments

I have a console application that sends emails to several (12 currently) different email groups. The process loops through and each time creates a different excel workbook, saves the workbook, then sends an email with this workbook attached.
This process ran great for several months. Now, the program will make it through 2 emails, then it will throw an exception for Failure sending mail. I have found the inner exception is Unable to read data from the transport connection: net_io_connectionclosed
The workbooks are created via 2 SQL stored procs. Even when the email fails, the workbooks are created and can be opened so I don't think it has anything to do with the SQL involved.
I have seen several other SO issues for similar issues, but since my process works for 2, fails for 1, works for 2, fails for 1.... I believe my issue is different than a simple port or credentials issue.
Here is the code I have before loop to declare the SmtpClient
SmtpClient client = new SmtpClient("intmail.MyDomain.net", 25);
NetworkCredential cred = new NetworkCredential("myEmailAddress#email.com", "");
client.Credentials = cred; // Send our account login details to the client.
client.EnableSsl = false; // Read below.
Here is my code inside the loop for creating email, attaching workbook, and sending.
MailMessage msg = new MailMessage();
Attachment xls01 = new Attachment(filePath);
string emailTo = ClientEmail;
string[] emailTos = emailTo.Split(new char[] { ';' });
foreach (string To in emailTos)
{
msg.To.Add(To);
Console.WriteLine(To);
}
msg.From = new MailAddress("myEmailAddress#email.com");
//msg.CC.Add(new MailAddress("myEmailAddress#email.com"));
msg.Subject = ClientName + reportMonth + " " + reportYear;
msg.Body = "Attached is report for period ending on the last day of " + reportMonth + " " + reportYear + ".\r\nAlso attached are the 13 month trends.";
msg.Attachments.Add(xls01);
try
{
client.Send(msg);
}
catch (Exception ex)
{
Console.WriteLine(ex); //Should print stacktrace + details of inner exception
if (ex.InnerException != null)
{
Console.WriteLine("InnerException is: {0}", ex.InnerException);
}
}
Most likely you are hitting smtp server antispam filter when you sending messages one right after another. Try to space them apart in time and make 3 attempts for each send. Obviously you should not stop if any one send is failed, you should go through all 12.

How to add attachments to mailto in c#?

string email ="sample#gmail.com";
attachment = path + "/" + filename;
Application.OpenURL ("mailto:" +
email+"
?subject=EmailSubject&body=EmailBody"+"&attachment="+attachment);
In the above code, attachment isn't working. Is there any other alternative to add attachments using a mailto: link in C#?
mailto: doesn't officially support attachments. I've heard Outlook 2003 will work with this syntax:
<a href='mailto:name#domain.com?Subject=SubjTxt&Body=Bod_Txt&Attachment=""C:\file.txt"" '>
Your problem has already been answered:
c-sharp-mailto-with-attachment
You can use the System.Net.Mail which has the MailMessage.Attachments Property. Something like:
message.Attachments.Add(new Attachment(yourAttachmentPath));
OR
You can try like this:
using SendFileTo;
namespace TestSendTo
{
public partial class Form1 : Form
{
private void btnSend_Click(object sender, EventArgs e)
{
MAPI mapi = new MAPI();
mapi.AddAttachment("c:\\temp\\file1.txt");
mapi.AddAttachment("c:\\temp\\file2.txt");
mapi.AddRecipientTo("person1#somewhere.com");
mapi.AddRecipientTo("person2#somewhere.com");
mapi.SendMailPopup("testing", "body text");
// Or if you want try and do a direct send without displaying the
// mail dialog mapi.SendMailDirect("testing", "body text");
}
}
}
The above code uses the MAPI32.dll.
Source
It probably won't attach the document because you are at the liberty
of the email client to implement the mailto protocol and include
parsing for the attachment clause. You may not know what mail client
is installed on the PC, so it may not always work - Outlook certainly
doesn't support attachments using mailto.
A better way to handle this is to send the mail on the server using System.Net.Mail.Attachment.
public static void CreateMessageWithAttachment(string server)
{
// Specify the file to be attached and sent.
// This example assumes that a file named Data.xls exists in the
// current working directory.
string file = "data.xls";
// Create a message and set up the recipients.
MailMessage message = new MailMessage(
"jane#contoso.com",
"ben#contoso.com",
"Quarterly data report.",
"See the attached spreadsheet.");
// Create the file attachment for this e-mail message.
Attachment data = new Attachment(file, MediaTypeNames.Application.Octet);
// Add time stamp information for the file.
ContentDisposition disposition = data.ContentDisposition;
disposition.CreationDate = System.IO.File.GetCreationTime(file);
disposition.ModificationDate = System.IO.File.GetLastWriteTime(file);
disposition.ReadDate = System.IO.File.GetLastAccessTime(file);
// Add the file attachment to this e-mail message.
message.Attachments.Add(data);
//Send the message.
SmtpClient client = new SmtpClient(server);
// Add credentials if the SMTP server requires them.
client.Credentials = CredentialCache.DefaultNetworkCredentials;
try
{
client.Send(message);
}
catch (Exception ex)
{
Console.WriteLine("Exception caught in CreateMessageWithAttachment(): {0}", ex.ToString());
}
data.Dispose();
}

System.Net.Mail.SmtpException: Insufficient system storage. The server response was: 4.3.1 Insufficient system resources

I've recently designed a program in C# that will pull information from SQL databases, write an HTML page with the results, and auto-email it out. I've got everything working [sporadically], the problem I'm having is that I seem to be crashing our company's exchange server. After the first few successful runs of the program, I'll start getting this exception:
Base exception: System.Net.Mail.SmtpException: Insufficient system storage. The server response was: 4.3.1 Insufficient system resources
I'm wondering if I should be calling some sort of Dispose()-like method in my mailing? Or if there is any other apparent reason that I would be causing the mail system to stop responding? This affects all clients in our company, not just my code.
This is Exchange 2010, and my code is compiled against .NET 3.5. My attachments are typically 27kb. If I log into the exchange server, it seems that messages just stick in a queue indefinitely. Clearing out the queue (remove without sending NDR) and rebooting the server will get it going again.
The mailing portions look like this (username, password, and address changed):
public void doFinalEmail()
{
List<string> distList = new List<string>();
string distListPath = Environment.CurrentDirectory + "\\DistList.txt";
string aLine;
logThat("Attempting email distribution of the generated report.");
if (File.Exists(distListPath))
{
FileInfo distFile = new FileInfo(distListPath);
StreamReader distReader = distFile.OpenText();
while (!String.IsNullOrEmpty(aLine = distReader.ReadLine()))
{
distList.Add(aLine);
}
}
else
{
logThat("[[ERROR]]: Distribution List DOES NOT EXIST! Path: " + distListPath);
}
MailMessage msg = new MailMessage();
MailAddress fromAddress = new MailAddress("emailaddresshere");
msg.From = fromAddress;
logThat("Recipients: ");
foreach (string anAddr in distList)
{
msg.To.Add(anAddr);
logThat("\t" + anAddr);
}
if (File.Exists(Program.fullExportPath))
{
logThat("Attachment: " + Program.fullExportPath);
Attachment mailAttachment = new Attachment(Program.fullExportPath);
msg.Attachments.Add(mailAttachment);
string subj = "Company: " + Program.yestFileName;
msg.Subject = subj;
msg.IsBodyHtml = true;
msg.BodyEncoding = System.Text.Encoding.UTF8;
sendMail(msg);
}
else
{
logThat("[[ERROR]]: ATTACHMENT DOES NOT EXIST! Path: " + Program.fullExportPath);
}
}
public void sendMail(MailMessage msg)
{
try
{
string username = "user"; //domain user
string password = "pass"; // password
SmtpClient mClient = new SmtpClient();
mClient.Host = "192.168.254.11";
mClient.Credentials = new NetworkCredential(username, password);
mClient.DeliveryMethod = SmtpDeliveryMethod.Network;
mClient.Send(msg);
}
catch (Exception oops)
{
string whatHappened = String.Format("Company: \r\nFailure in {0}! \r\n\r\nError message: {1} \r\nError data: {2} \r\n\r\nStack trace: {3} \r\n\r\nBase exception: {4} \r\nOccuring in method: {5} with a type of {6}\r\n", oops.Source, oops.Message, oops.Data, oops.StackTrace, oops.GetBaseException(), oops.TargetSite, oops.GetType());
logThat(whatHappened);
Environment.Exit(1);
}
}
This error can happen when:
The exchange server is out of disk space.
The recipient mailbox is out of disk space.
It is more common to run into issue #2 than issue #1.
Here is a list of Exchange Status Codes and their meanings.
To narrow down the issue definitively, you could swap in a different mail client like aspNetEmail (you can get an eval version to test), it wouldn't take but a few lines of code. If the problem persists, it is on the server; if not, it is on the client. I would strongly suspect this is a problem on the server, however, as when the client closes the connection and the message is sent, the server should really not be holding any resources as a result of that connection. You could verify this by looking at your SMTP logs and making sure there was no SMTP error when the connection was closed.
If this is indeed a problem on the server (the SMTP log shows clean disconnection and the problem is replicable with an alternate client), then I would log onto the server (if you can get access), and watch the disk space as jeuton suggests.
In exchange 2007 the c: (system drive) needs about 4GB of free disk space. This was our problem. Increment the drive and the mails will flow again.

Transfer file from Windows Mobile device to...anywhere

I can't seem to find a solution to this issue. I'm trying to get my Compact Framework application on Windows Mobile 6 to have the ability to move a file on its local filesystem to another system.
Here's the solutions I'm aware of:
FTP - Problem with that is most of
the APIs are way to expensive to use.
HTTP PUT - As far as I have been able to find, I can't use anonymous PUT with IIS7, and that's the web server the system is running. (An extreme workaround for this would be to use a different web server to PUT the file, and have that other system transfer it to the IIS system).
Windows share - I would need authentication on the shares, and I haven't seen that a way to pass this authentication through windows mobile.
The last resort would be to require that the devices be cradled to transfer these files, but I'd really like to be able to have these files be transferred wirelessly.
FTP: define "too expensive". Do you mean performance or byte overhead or dollar cost? Here's a free one with source.
HTTP: IIS7 certainly supports hosting web services or custom IHttpHandlers. You could use either for a data upload pretty easily.
A Windows Share simply requires that you to P/Invoke the WNet APIs to map the share, but it's not terribly complex.
I ended up just passing information to a web server via a PHP script.
The options provided above just didn't work out for my situation.
Here's the gist of it. I've got some code in there with progress bars and various checks and handlers unrelated to simply sending a file, but I'm sure you can pick through it. I've removed my authentication code from both the C# and the PHP, but it shouldn't be too hard to roll your own, if necessary.
in C#:
/*
* Here's the short+sweet about how I'm doing this
* 1) Copy the file from mobile device to web server by querying PHP script with paramaters for each line
* 2) PHP script checks 1) If we got the whole data file 2) If this is a duplicate data file
* 3) If it is a duplicate, or we didn't get the whole thing, it goes away. The mobile
* device will hang on to it's data file in the first case (if it's duplicate it deletes it)
* to be tried again later
* 4) The server will then process the data files using a scheduled task/cron job at an appropriate time
*/
private void process_attempts()
{
Uri CheckUrl = new Uri("http://path/to/php/script?action=check");
WebRequest checkReq = WebRequest.Create(CheckUrl);
try
{
WebResponse CheckResp = checkReq.GetResponse();
CheckResp.Close();
}
catch
{
MessageBox.Show("Error! Connection not available. Please make sure you are online.");
this.Invoke(new Close(closeme));
}
StreamReader dataReader = File.OpenText(datafile);
String line = null;
line = dataReader.ReadLine();
while (line != null)
{
Uri Url = new Uri("http://path/to/php/script?action=process&line=" + line);
WebRequest WebReq = WebRequest.Create(Url);
try
{
WebResponse Resp = WebReq.GetResponse();
Resp.Close();
}
catch
{
MessageBox.Show("Error! Connection not available. Please make sure you are online.");
this.Invoke(new Close(closeme));
return;
}
try
{
process_bar.Invoke(new SetInt(SetBarValue), new object[] { processed });
}
catch { }
process_num.Invoke(new SetString(SetNumValue), new object[] { processed + "/" + attempts });
processed++;
line = dataReader.ReadLine();
}
dataReader.Close();
Uri Url2 = new Uri("http://path/to/php/script?action=finalize&lines=" + attempts);
Boolean finalized = false;
WebRequest WebReq2 = WebRequest.Create(Url2);
try
{
WebResponse Resp = WebReq2.GetResponse();
Resp.Close();
finalized = true;
}
catch
{
MessageBox.Show("Error! Connection not available. Please make sure you are online.");
this.Invoke(new Close(closeme));
finalized = false;
}
MessageBox.Show("Done!");
this.Invoke(new Close(closeme));
}
In PHP (thoroughly commented for your benefit!):
<?php
//Get the GET'd values from the C#
//The current line being processed
$line = $_GET['line'];
//Which action we are doing
$action = $_GET['action'];
//# of lines in the source file
$totalLines = $_GET['lines'];
//If we are processing the line, open the data file, and append this new line and a newline.
if($action == "process"){
$dataFile = "tempdata/SOME_KIND_OF_UNIQUE_FILENAME.dat";
//open the file
$fh = fopen($dataFile, 'a');
//Write the line, and a newline to the file
fwrite($fh, $line."\r\n");
//Close the file
fclose($fh);
//Exit the script
exit();
}
//If we are done processing the original file from the C# application, make sure the number of lines in the new file matches that in the
//file we are transferring. An expansion of this could be to compare some kind of hash function value of both files...
if($action == "finalize"){
$dataFile = "tempdata/SOME_KIND_OF_UNIQUE_FILENAME.dat";
//Count the number of lines in the new file
$lines = count(file($dataFile));
//If the new file and the old file have the same number of lines...
if($lines == $totalLines){
//File has the matching number of lines, good enough for me over TCP.
//We should move or rename this file.
}else{
//File does NOT have the same number of lines as the source file.
}
exit();
}
if($action == "check"){
//If a file with this unique file name already exists, delete it.
$dataFile = "tempdata/SOME_KIND_OF_UNIQUE_FILENAME.dat";
if(file_exists($dataFile)){
unlink($dataFile);
}
}
?>

Categories