0
votes

I am currently working on a C# project where I am making my own SMTP Server. It is more a less working but I am trying to make it work where I can send an email to multiple recipients that are on different domains.

I did initially do it so I would create a MailMesssage object and run the following to add each recipient

MailMessage message = new MailMessage();
message.To.add("[email protected]");
message.To.add("[email protected]");

If the different domains but going through the google app servers, I would get the MX record which was ALT2.ASPMX.L.GOOGLE.COM. When sending the mail though with the recipients add above google would send an error back as they do not allow cross domain sending via one SMTP session.

Therefore I have reworked it so a separate email is sent for each recipient, I get the MX record for each domain so I end up as well with a different SMTP session. So there would only be one message.To.add for each recipient I received. What I am trying to do is add a header so it still shows that the email recipients are still going to [email protected] and [email protected].

Therefore as far as the MailMessage component is concerned there is one recipient but the headers show multiple recipients so when the received of the mail views the email in their client it shows all the recipients that the email went to.

Below is the code I have to send the email.

MXLookup mxLookup = new MXLookup();
                    List<string> recipients = addRecipientsToEmail(message.emailRecipients);
                    foreach (string recipient in recipients)
                    {
                        string domain = Classes.CommonTasks.getDomainFromEmail(recipient);
                        string[] mxRecords = mxLookup.getMXRecords(Classes.CommonTasks.getDomainFromEmail(domain));
                        if (mxRecords != null)
                        {
                            MailMessage composedMail = new MailMessage();
                            composedMail.From = new MailAddress(message.EmailFromAddress);
                            composedMail.To.Add(recipient);
                            composedMail.Subject = message.subject;
                            composedMail.Body = message.EmailBody;
                            composedMail.Headers.Add(getHeaders(recipients));

                            if (message.contentType.ToString().Contains("text/html"))
                            {
                                composedMail.IsBodyHtml = true;
                            }

                            SmtpClient smtp = new SmtpClient(mxRecords[0]);
                            smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
                            smtp.Port = 25;
                            if (Configuration.emailConfig.useSmtpMaxIdleTime)
                            {
                                smtp.ServicePoint.MaxIdleTime = 1;
                            }
                            library.logging(methodInfo, string.Format("Sending email via MX Record: {0}", mxRecords[0]));
                            smtp.Send(composedMail);
                            updateEmailStatus(message.emailID, EmailStatus.Sent);
                            library.logging(methodInfo, string.Format("Successfully sent email ID: {0}", message.emailID));

                        }

                        else
                        {
                            string error = string.Format("No MX Record found for domain: {0}", domain);
                            library.logging(methodInfo, error);
                            library.setAlarm(error, CommonTasks.AlarmStatus.Warning, methodInfo);
                        }

Below is my getHeaders function.

private NameValueCollection getHeaders(List<string> emailRecipients)
{
    string headers = "";
    NameValueCollection headersArray = new NameValueCollection();
    foreach (string recipient in emailRecipients)
    {
        headers += string.Format("{0}, ", recipient);
    }
    headersArray.Add("To", headers);
    return headersArray;
}

Thanks for any help you can provide.

1
If you are actually writing your own smtp server why are you sending through google? Rhetorical. Anyway, you have two choices. choice 1: Follow google.com's guidelines and send 1 message for each person. I'm sure they have spent a fair amount of time ensuring people don't get around their restriction. Choice 2: change email providers, or simply setup your own mail server. - NotMe
I'm sending via the MX record the recipients domain as stated in my question. I just happen to be using google app email domain so the email is being relayed via Google MX record - Boardy

1 Answers

1
votes

Boardy, first, let me say that I understand why you are doing your own SMTP server as opposed to relaying. We are doing the same thing because we need to process the email failures real-time and not have to rely on badmail or NDRs. That is only practical if you control the transmission channel.

The problem you have here is that there is no way to separate RCPT TO command and the 'To' header. If you look at wireshark log for Exchange (i.e.) sending an email that is as you outlined above, it will have two port 25 (presumably) sessions, once to domain1 and again to domain2. In the first session, RCPT TO will be [email protected] and To header will be To: [email protected], [email protected]. For the second session, the To header will be identical but RCPT TO will be [email protected]. In the .net class, To collection is really RCPT TO and it seems we have no access to the To header. Of course, if you try that and actually add both recipients domain1 will give you relay error for [email protected] (and vice-versa). Ideally, we would have RCPT collection we can set as recipients, and To (and CC) collections can be a header wrapper that get defaulted based on RCPT info or can we overridden. As it stands now, there is no way using .net classes to really do your own mail server implementation.

I have struggled with this for a few years. It has just recently become a problem as we started using CC and multiple Tos so I am looking into a solution. Currently I am considering either using a custom sockets implementation or using Rebex. They seem to have what I need but I can't say I have gotten it to work yet as I want it. Of course, if there is some great reflection trick (like we do with FQDN) to make this work natively that would be great to know. In short, answer to your question is AFAIK, can't do that. I will post back any experience I have with Rebex.