Contacts.Update() is fully “synchronous”, meaning that when the call returns with a success value, the contact IS updated, that is an absolute and inescapable fact, and there is no possible way whatsoever that anything done AFTER that, could possibly read the old values. Contact.Update() does a direct SQL “UPDATE” on the row in the ContactList table for that contact, and only after the operation has worked and the transaction committed, is the answer send back to the caller.
Messages.SendSingle(), however, does not work like that. What it does :
- Validate the data you send (check that the idMessage refers to a proper message that can be sent in the project, checks to discover what contacts you are targeting)
- It then INSERTS an “OUTBOX” record, that requests that this idMessage be send to this(these) idContact(s) as soon as feasible
- Once the request has been recorded properly, the service returns a success
This means that the message is sent ASYNCHRONOUSLY, at a later point. The way it works:
Our email production service have a process that monitor that “OUTBOX” list.
Whenever it sees a new record in it, it fetches the record, and the data associated with it.
Specifically, it loads the message, and it loads the Contact, and then it produces and sends the message.
- The message is sent using Contact’s data, and Message’s content that are valid at the time the mailer service picks up the OUTBOX record
- The service handles a lot of work, so there is going to be a delay between the time the OUTBOX record is written, and the time that it’s picked up (along with the Contact and Message) by the service. That delay can be as short as ~0.1 seconds, but can also be as long as “many minutes” if there is a large backlog to catch up on for instance. The AVERAGE delay will be between 0.5 and 1 seconds.
Let me draw a quick timeline in an Excel worksheet to illustrate how this causes your problem.
Let’s look at your operations, when done with zero delays:
(see attache fig-1)
- Update, Send, Update, Send, Update, Send. At the end of your cycle, the contact has info from the 3rd update. This is all done within about 0.25 seconds.
- Then, the Mailer service sees the 3 requests to send a message. Each 3 times, it loads the appropriate message and contact. Except by that time, the contact has values from the 3rd update, so all 3 messages go out with data from that 3rd update.
Now let’s see why doing it with a 1s delay SEEMS to work (it will not work all the time):
(See attached fig-2)
- Update, Send, Wait 1 second
- There is a GOOD CHANCE that the Mailer will process that request, therefore load version 1 of the contact, before you do your next loop, because your 1 second delay is higher than the AVERAGE time it takes the mailer service to see a send request
- Update, Send, Wait 1 second
- Again, good chance that the mailer will see it before the 3rd update
- And so on.
The key point here is that there is absolutely no guarantee that it will work like that, since the mailer service might be busy with a sudden influx of requests and could take 5, 10, 20 seconds to get to that request. Or worse, the service could be offline while we’re publishing an update or a bug fix and will be back in 4-5 minutes only.
So to repeate and summarize:
It is absolutely IMPOSSIBLE to reliably create a loop that will update a single contact and then send a message, and repeat this over and over for the same contact. Even if you insert a delay in your loop, there is a risk, the shorter the delay is, that eventually 2 updates will go through before the mailer service picked up the send request.
THAT'S WHY, BEFORE ANY COMPLEX PROCESS IMPLEMENTATION, SEEK ADVISE FROM US, WE'LL FIND A SOLUTION.