using System; using System.Collections.Generic; using System.Text; using System.Linq; using System.IO; using MailKit.Net.Smtp; using MailKit.Net.Imap; using MailKit.Search; using MailKit; using MimeKit; namespace rockfishCore.Util { //http://www.mimekit.net/ public static class RfMail { public const string MAIL_SMPT_ADDRESS = "smtp.ayanova.com"; public const int MAIL_SMPT_PORT = 465; public const string MAIL_IMAP_ADDRESS = "mail.ayanova.com"; public const int MAIL_IMAP_PORT = 993; public const string MAIL_ACCOUNT_SUPPORT = "support@ayanova.com"; public const string MAIL_ACCOUNT_PASSWORD_SUPPORT = "e527b6c5a00c27bb61ca694b3de0ee178cbe3f1541a772774762ed48e9caf5ce"; public const string MAIL_ACCOUNT_SALES = "sales@ayanova.com"; public const string MAIL_ACCOUNT_PASSWORD_SALES = "6edbae5eb616a975abf86bcd9f45616f5c70c4f05189af60a1caaa62b406149d"; public enum rfMailAccount { support = 1, sales = 2 } public class rfMailMessage { public MimeMessage message; public uint uid; } class DeliverStatusSmtpClient : SmtpClient { protected override DeliveryStatusNotification? GetDeliveryStatusNotifications(MimeMessage message, MailboxAddress mailbox) { return DeliveryStatusNotification.Delay | DeliveryStatusNotification.Failure | DeliveryStatusNotification.Success; } } ///////////////////////////////////////////////////////// // // Do the sending with optional deliver status receipt // public static void DoSend(MimeMessage message, string MailAccount, string MailAccountPassword, bool trackDeliveryStatus) { if (trackDeliveryStatus) { //set the return receipt and disposition to headers message.Headers.Add("Return-Receipt-To", "<" + MailAccount + ">"); message.Headers.Add("Disposition-Notification-To", "<" + MailAccount + ">"); using (var client = new DeliverStatusSmtpClient()) { //Accept all SSL certificates (in case the server supports STARTTLS) //(we have a funky cert on the mail server) client.ServerCertificateValidationCallback = (s, c, h, e) => true; client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true); // Note: since we don't have an OAuth2 token, disable // the XOAUTH2 authentication mechanism. client.AuthenticationMechanisms.Remove("XOAUTH2"); // Note: only needed if the SMTP server requires authentication client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT); client.Send(message); client.Disconnect(true); } } else { using (var client = new SmtpClient()) { //Accept all SSL certificates (in case the server supports STARTTLS) //(we have a funky cert on the mail server) client.ServerCertificateValidationCallback = (s, c, h, e) => true; client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true); // Note: since we don't have an OAuth2 token, disable // the XOAUTH2 authentication mechanism. client.AuthenticationMechanisms.Remove("XOAUTH2"); // Note: only needed if the SMTP server requires authentication client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT); client.Send(message); client.Disconnect(true); } } } public static void SendMessage(string MessageFrom, string MessageTo, string MessageSubject, string MessageBody, bool trackDeliveryStatus = false, string CustomHeader = "", string CustomHeaderValue = "") { var message = new MimeMessage(); message.From.Add(new MailboxAddress(MessageFrom)); message.To.Add(new MailboxAddress(MessageTo)); message.Subject = MessageSubject; message.Body = new TextPart("plain") { Text = MessageBody }; message.Headers["X-Mailer"] = RfVersion.Full; if (CustomHeader != "" && CustomHeaderValue != "") { message.Headers["X-Rockfish-" + CustomHeader] = CustomHeaderValue; } //send with optional tracking DoSend(message, MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT, trackDeliveryStatus); // using (var client = new SmtpClient()) // { // //Accept all SSL certificates (in case the server supports STARTTLS) // //(we have a funky cert on the mail server) // client.ServerCertificateValidationCallback = (s, c, h, e) => true; // client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true); // // Note: since we don't have an OAuth2 token, disable // // the XOAUTH2 authentication mechanism. // client.AuthenticationMechanisms.Remove("XOAUTH2"); // // Note: only needed if the SMTP server requires authentication // client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT); // client.Send(message); // client.Disconnect(true); // } }//send message public static void ReplyMessage(uint replyToMessageId, rfMailAccount replyFromAccount, string replyBody, bool replyToAll, bool trackDeliveryStatus = false, string CustomHeader = "", string CustomHeaderValue = "") { //get the original to reply to it: MimeMessage message = GetMessage(replyToMessageId, replyFromAccount); if (message == null) { throw new System.ArgumentException("RfMail:ReplyMessage->source message not found (id=" + replyToMessageId.ToString() + ")"); } //construct the new message var reply = new MimeMessage(); MailboxAddress from = null; string from_account = ""; string from_account_password = ""; switch (replyFromAccount) { case rfMailAccount.sales: from_account = MAIL_ACCOUNT_SALES; from_account_password = MAIL_ACCOUNT_PASSWORD_SALES; from = new MailboxAddress(from_account); break; default: from_account = MAIL_ACCOUNT_SUPPORT; from_account_password = MAIL_ACCOUNT_PASSWORD_SUPPORT; from = new MailboxAddress(from_account); break; } reply.From.Add(from); // reply to the sender of the message if (message.ReplyTo.Count > 0) { reply.To.AddRange(message.ReplyTo); } else if (message.From.Count > 0) { reply.To.AddRange(message.From); } else if (message.Sender != null) { reply.To.Add(message.Sender); } if (replyToAll) { // include all of the other original recipients (removing ourselves from the list) reply.To.AddRange(message.To.Mailboxes.Where(x => x.Address != from.Address)); reply.Cc.AddRange(message.Cc.Mailboxes.Where(x => x.Address != from.Address)); } // set the reply subject if (!message.Subject.StartsWith("Re:", StringComparison.OrdinalIgnoreCase)) reply.Subject = "Re: " + message.Subject; else reply.Subject = message.Subject; // construct the In-Reply-To and References headers if (!string.IsNullOrEmpty(message.MessageId)) { reply.InReplyTo = message.MessageId; foreach (var id in message.References) reply.References.Add(id); reply.References.Add(message.MessageId); } // quote the original message text using (var quoted = new StringWriter()) { var sender = message.Sender ?? message.From.Mailboxes.FirstOrDefault(); var name = sender != null ? (!string.IsNullOrEmpty(sender.Name) ? sender.Name : sender.Address) : "someone"; quoted.WriteLine("On {0}, {1} wrote:", message.Date.ToString("f"), name); using (var reader = new StringReader(message.TextBody)) { string line; while ((line = reader.ReadLine()) != null) { quoted.Write("> "); quoted.WriteLine(line); } } reply.Body = new TextPart("plain") { Text = replyBody + "\r\n\r\n\r\n" + quoted.ToString() }; } reply.Headers["X-Mailer"] = RfVersion.Full; if (CustomHeader != "" && CustomHeaderValue != "") { reply.Headers["X-Rockfish-" + CustomHeader] = CustomHeaderValue; } //send with optional tracking DoSend(reply, from_account, from_account_password, trackDeliveryStatus); // using (var client = new SmtpClient()) // { // //Accept all SSL certificates (in case the server supports STARTTLS) // //(we have a funky cert on the mail server) // client.ServerCertificateValidationCallback = (s, c, h, e) => true; // client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true); // // Note: since we don't have an OAuth2 token, disable // // the XOAUTH2 authentication mechanism. // client.AuthenticationMechanisms.Remove("XOAUTH2"); // // Note: only needed if the SMTP server requires authentication // client.Authenticate(from_account, from_account_password); // client.Send(reply); // client.Disconnect(true); // } //flag the message as having been replied to FlagInboxMessageSeenReplied(replyToMessageId, replyFromAccount); }//send message //Fetch message by UID public static MimeMessage GetMessage(uint uid, rfMailAccount fromAccount = rfMailAccount.support) { using (var client = new ImapClient()) { // Accept all SSL certificates client.ServerCertificateValidationCallback = (s, c, h, e) => true; client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true); // Note: since we don't have an OAuth2 token, disable // the XOAUTH2 authentication mechanism. client.AuthenticationMechanisms.Remove("XOAUTH2"); if (fromAccount == rfMailAccount.support) { client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT); } else { client.Authenticate(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES); } var inbox = client.Inbox; inbox.Open(FolderAccess.ReadOnly); var m = inbox.GetMessage(new UniqueId(uid)); client.Disconnect(true); return m; } }//get message //Fetch messages by Search query public static List GetMessages(SearchQuery query) { List ret = new List(); using (var client = new ImapClient()) { // Accept all SSL certificates client.ServerCertificateValidationCallback = (s, c, h, e) => true; client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true); // Note: since we don't have an OAuth2 token, disable // the XOAUTH2 authentication mechanism. client.AuthenticationMechanisms.Remove("XOAUTH2"); client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT); var inbox = client.Inbox; inbox.Open(FolderAccess.ReadOnly); foreach (var uid in inbox.Search(query)) { ret.Add(new rfMailMessage { message = inbox.GetMessage(uid), uid = uid.Id }); } client.Disconnect(true); } return ret; }//get message //Flag message as seen and replied by UID public static bool FlagInboxMessageSeenReplied(uint uid, rfMailAccount inAccount = rfMailAccount.support) { using (var client = new ImapClient()) { // Accept all SSL certificates client.ServerCertificateValidationCallback = (s, c, h, e) => true; client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true); // Note: since we don't have an OAuth2 token, disable // the XOAUTH2 authentication mechanism. client.AuthenticationMechanisms.Remove("XOAUTH2"); if (inAccount == rfMailAccount.support) { client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT); } else { client.Authenticate(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES); } var inbox = client.Inbox; inbox.Open(FolderAccess.ReadWrite); inbox.AddFlags(new UniqueId(uid), MessageFlags.Seen, true); inbox.AddFlags(new UniqueId(uid), MessageFlags.Answered, true); client.Disconnect(true); return true; } }//get message //////////////////////////////////////////////////// //Put a message in the drafts folder of support // public static void DraftMessage(string MessageFrom, string MessageTo, string MessageSubject, string MessageBody, string CustomHeader = "", string CustomHeaderValue = "") { var message = new MimeMessage(); message.From.Add(new MailboxAddress(MessageFrom)); //case 3512 handle more than one email in the address if (MessageTo.Contains(",")) { List mbAll = new List(); var addrs = MessageTo.Split(','); foreach (string addr in addrs) { mbAll.Add(new MailboxAddress(addr.Trim())); } message.To.AddRange(mbAll); } else { message.To.Add(new MailboxAddress(MessageTo)); } message.Subject = MessageSubject; message.Body = new TextPart("plain") { Text = MessageBody }; if (CustomHeader != "" && CustomHeaderValue != "") { message.Headers["X-Rockfish-" + CustomHeader] = CustomHeaderValue; } //adapted from https://stackoverflow.com/questions/33365072/mailkit-sending-drafts using (var client = new ImapClient()) { try { // Accept all SSL certificates client.ServerCertificateValidationCallback = (s, c, h, e) => true; client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true); // Note: since we don't have an OAuth2 token, disable // the XOAUTH2 authentication mechanism. client.AuthenticationMechanisms.Remove("XOAUTH2"); client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT); var draftFolder = client.GetFolder("Drafts");//Our surgemail server works with this other servers in future might not if (draftFolder != null) { draftFolder.Open(FolderAccess.ReadWrite); draftFolder.Append(message, MessageFlags.Draft); //draftFolder.Expunge(); } } catch (Exception ex) { throw new System.Exception("RfMail->DraftMessage() - Exception has occured: " + ex.Message); } client.Disconnect(true); } }//draft message ///////////////////////////////////////////////////////////////// //Fetch summaries of unread messages in sales and support //inboxes // public static List GetSalesAndSupportSummaries() { List ret = new List(); ret.AddRange(getInboxSummariesFor(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT)); ret.AddRange(getInboxSummariesFor(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES)); return ret; } private static List getInboxSummariesFor(string sourceAccount, string sourcePassword) { List ret = new List(); using (var client = new ImapClient()) { // Accept all SSL certificates client.ServerCertificateValidationCallback = (s, c, h, e) => true; client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true); // Note: since we don't have an OAuth2 token, disable // the XOAUTH2 authentication mechanism. client.AuthenticationMechanisms.Remove("XOAUTH2"); client.Authenticate(sourceAccount, sourcePassword); var inbox = client.Inbox; inbox.Open(FolderAccess.ReadOnly); var summaries = inbox.Fetch(0, -1, MessageSummaryItems.Full | MessageSummaryItems.UniqueId); client.Disconnect(true); foreach (var summary in summaries) { //Sometimes bad hombres don't set a from address so don't expect one string sFrom = "UNKNOWN / NOT SET"; if (summary.Envelope.From.Count > 0) { sFrom = summary.Envelope.From[0].ToString().Replace("\"", "").Replace("<", "").Replace(">", "").Trim(); } ret.Add(new rfMessageSummary { account = sourceAccount, id = summary.UniqueId.Id, subject = summary.Envelope.Subject, sent = DateUtil.DateTimeOffSetNullableToEpoch(summary.Envelope.Date), from = sFrom, flags = summary.Flags.ToString().ToLowerInvariant() }); } } //reverse the results array as emails come in oldest first order but we want oldest last ret.Reverse(); return ret; } public class rfMessageSummary { public string account; public uint id; public string from; public string subject; public long? sent; public string flags; } //Fetch a single string preview of message by Account / folder / UID public static rfMessagePreview GetMessagePreview(string mailAccount, string mailFolder, uint uid) { using (var client = new ImapClient()) { // Accept all SSL certificates client.ServerCertificateValidationCallback = (s, c, h, e) => true; client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true); // Note: since we don't have an OAuth2 token, disable // the XOAUTH2 authentication mechanism. client.AuthenticationMechanisms.Remove("XOAUTH2"); //TODO: make accounts reside in dictionary in future if (mailAccount == "support@ayanova.com") { client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT); } if (mailAccount == "sales@ayanova.com") { client.Authenticate(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES); } var fldr = client.GetFolder(mailFolder); fldr.Open(FolderAccess.ReadOnly); var m = fldr.GetMessage(new UniqueId(uid)); client.Disconnect(true); StringBuilder sb = new StringBuilder(); sb.Append("From: "); sb.AppendLine(m.From[0].ToString().Replace("\"", "").Replace("<", "").Replace(">", "").Trim()); sb.Append("To: "); sb.AppendLine(mailAccount); sb.Append("Subject: "); sb.AppendLine(m.Subject); sb.AppendLine(); sb.AppendLine(); sb.AppendLine(m.GetTextBody(MimeKit.Text.TextFormat.Plain)); rfMessagePreview preview = new rfMessagePreview(); preview.id = uid; preview.preview = sb.ToString(); preview.isKeyRequest = m.Subject.StartsWith("Request for 30 day temporary"); return preview; } }//get message public class rfMessagePreview { public bool isKeyRequest; public string preview; public uint id; } //---------------- }//eoc }//eons