1

I am sending bulk emails to an corporation exchange server, using a client application written in C#.

It can happen, and it did, that the client application timeout (not the server). Since there is no way to know if the server completed the request, how to handle retrys for this case?

There are no ids involved that could be use to avoid duplicates. Setting long timeouts or even infinite timeouts is not a good policy.

I am using exponential backoff algorithm for retry. In this case it should only send only one duplicate because it will wait longer next time.

I think there is no bullet prof solution. Anyway, since it is first project of the kind, i need to check to see if someone has a solution that i miss looked.

Update: The exchange is doing the relay. I am using an SmtpClient to send e-mails. The problem is that the server can send the 250 Ok message, but the receiver never gets it, and then try again. That is the only problem i am trying to solve in this post.

In Rest services the recommended approach is to use an concurrency error. If the client post something and get "409 - conflict" status that means the message was already stored on the server. But for that to happen there as to be a key for the message that is created by the client and is part of the message. SMTP doesn't seem to have a mechanism that can prevent that.

MiguelSlv
  • 14,067
  • 15
  • 102
  • 169
  • Have you found any reasonable solution? – Michael Freidgeim Aug 05 '19 at 09:29
  • No. i can't guarantee no duplicates. Anyway, it seems to work fine using exponential back off strategy, so far. I am not aware of any duplicates. Setting the timeout value higher will make less like it. That's it the end of the road. – MiguelSlv Aug 05 '19 at 09:53

2 Answers2

0

Normally a SMTP server will give you an information if he accepted the email or not when using standard SMTP. Here is an example via telnet:

enter image description here enter image description here

So your application only need to track the response here and if a timesout happen you do not need to proceed with all emails and need to pick up where it stopped. By the way, your application should check every time the response as it could be that the sender or receiver isn´t accepted...

This is mentioned in RFC 5321 (you can scroll in that document):

When the receiver-SMTP accepts a piece of mail (by sending a "250 OK" message in response to DATA), it is accepting responsibility for delivering or relaying the message. It must take this responsibility seriously. It MUST NOT lose the message for frivolous reasons, such as because the host later crashes or because of a predictable resource shortage. [...] When the end of text is successfully received and stored, the SMTP-receiver sends a "250 OK" reply.

As you are sending emails I think section 4.5.4.1. might be important if you aren´t using some kind of framework which handle the outgoing emails RFC conform.

Example: Your client is generating an email but during the submit from the body (last part from the email transfer) the connection dropped. Then the server isn´t allowed to proceed the email. Technically he might got all infos, but the RFC didn´t allowed him to send that out as the client had a timeout and didn´t finished the whole process.

Update: The best way would be to use the C# SmtpClient method and then check the smtpStatusCode OK (= "The email was successfully sent to the SMTP service.") which you can see as well in the telnet examples. The methode isn´t doing anything else here (technically). The method is also compliance with RFC 5321 so you do not need to re-invent the smtp send wheel. If you do not get the OK something in the meantime might have had happen and you need to check the result and then perform an re-send (or need to give up if the error message is saying that the email address isn´t valid or something like that to avoid a infinite loop).

Community
  • 1
  • 1
BastianW
  • 2,628
  • 7
  • 29
  • 38
  • 2
    I don't understand. If the client timeout, it means that the client give up waiting for the server response, so he cannot know what was the outcome of the request because he will not be listening. Eg: if the communications were cut off, the server will proceed to the end, but the client will not receive the answer. Other example, the server was to busy, so it processed the request after 31 seconds but the client give up after 30. – MiguelSlv Sep 13 '17 at 17:50
  • Its quite simple: Every SMTP server those days must following [RFC 5321](https://tools.ietf.org/html/rfc5321#section-4.5.4.) _keep noted that you can scroll on that URL_ Your application must be conform to RFC 5321 here otherwise you will send multiple emails. That means if the proceed was cut in the middle, the server couldn´t proceed with the email, it will simply not work and would be against RFC 5321. That means if your application gets a 250 OK as mentioned in the RFC then the email is accepted by the remote server. If not, it must be resend. – BastianW Sep 13 '17 at 18:19
  • I am not sure if we are on the same page. The Microsoft exchange server is configure to do the relay the messages for me, so RFC 5321 should be ensure by this server. Do i need to comply with RFC 5321 in any way? I am sending one message at a time, checking the result before advance. In case of error, i retry 5 times, and that is where was my problem at first. If i got timeouts, how to proceed. – MiguelSlv Sep 13 '17 at 21:09
  • You act as an SMTP client and therefore need to be RFC 5321 compliance as well as you are the sender. Why didn´t you use the [SmtpClient](https://msdn.microsoft.com/en-us/library/swas0fwc(v=vs.110).aspx) method from C# which can handle [timeouts and exceptions](https://msdn.microsoft.com/en-us/library/system.net.mail.smtpexception(v=vs.110).aspx)? Then you should be able to catch the issue and perform an retry. You can then check for the 250 OK in the SMTPStatusCode [here](https://msdn.microsoft.com/en-us/library/system.net.mail.smtpstatuscode(v=vs.110).aspx) which i mentioned before. – BastianW Sep 13 '17 at 21:50
  • i am using the SmtpClient and handling exceptions. The bottom line is, the server can send the OK message, but the client never received for some our other reason. In Rest services, the recommended approach , it is to use an concurrency error. If the client post something and get "409 - conflict" status that means the message was already stored on the server. But for that to happen, there as to be a key for the message that is created by the client and is part of the message. So, i still don't see how i can handle the timeout properly. – MiguelSlv Sep 14 '17 at 10:03
  • its mostly similar like if two mailserver speak with each other and in the middle a network issue happen. Then then need to proceed with the resending. Unfortunately SMTP is quite old and not couldn´t be compared with rest services. However if you prefer rest, why didn´t use use the [Exchange Webservices EWS](https://msdn.microsoft.com/en-us/library/office/dd633628(v=exchg.80).aspx) to send emails? – BastianW Sep 14 '17 at 10:22
  • That may be an option. Has i say in the beginning, it happen. If you post another answer with this suggestion i will accept it. I will also clarify my question. Many thanks. – MiguelSlv Sep 14 '17 at 10:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/154434/discussion-between-bastianw-and-byteartisan). – BastianW Sep 14 '17 at 10:46
0

Another possible option might be to involve the Microsoft Exchange Webservices. You can use them as well to send out emails. Example from here:

// Create an email message and identify the Exchange service.
EmailMessage message = new EmailMessage(service);

// Add properties to the email message.
message.Subject = "Interesting";
message.Body = "The merger is finalized.";
message.ToRecipients.Add("user1@contoso.com");

// Send the email message and save a copy.
message.SendAndSaveCopy();

They give you as well a response back:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <h:ServerVersionInfo MajorVersion="14" 
               MinorVersion="0" 
               MajorBuildNumber="639" 
               MinorBuildNumber="20" 
               Version="Exchange2010" 
               xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" 
               xmlns="http://schemas.microsoft.com/exchange/services/2006/types" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xm=""lns:xsd="http://www.w3.org/2001/XMLSchema" />
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <m:CreateItemResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" 
               xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
      <m:ResponseMessages>
        <m:CreateItemResponseMessage ResponseClass="Success">
          <m:ResponseCode>NoError</m:ResponseCode>
          <m:Items />
        </m:CreateItemResponseMessage>
      </m:ResponseMessages>
    </m:CreateItemResponse>
  </s:Body>
</s:Envelope>
BastianW
  • 2,628
  • 7
  • 29
  • 38