18using System.Collections.Generic;
21using System.Threading.Tasks;
26 using System.Collections.Generic;
28 using MailKit.Net.Imap;
29 using MailKit.Net.Pop3;
31 using MailKit.Net.Smtp;
56 private GPALMailSettings gPALMailSettings;
57 private static string MailSettingsFilePath {
get;
set; } =
"./GPALMail.yaml";
62 private GPALMailSettings GPALMailSettings
66 return gPALMailSettings;
70 gPALMailSettings = value;
79 GPALMailSettings =
new GPALMailSettings();
80 GPALMailSettings.ToEmailAddresses =
new List<MailboxAddress>();
81 GPALMailSettings.CcEmailAddresses =
new List<MailboxAddress>();
82 GPALMailSettings.BccEmailAddresses =
new List<MailboxAddress>();
83 GPALMailSettings.Attachments =
new List<string>();
84 GPALMailSettings.ReceivedEmails =
new List<ReceivedEmail>();
104 GPALMailSettings.IMAPServerName = serverName;
105 GPALMailSettings.POPServerName =
null;
117 GPALMailSettings.POPServerName = serverName;
118 GPALMailSettings.IMAPServerName =
null;
129 GPALMailSettings.SMTPServerName = serverName;
140 GPALMailSettings.IMAPPortNum = portNum;
150 GPALMailSettings.POPPortNum = portNum;
160 GPALMailSettings.SMTPPortNum = portNum;
173 GPALMailSettings.AcceptAnyCertificate = acceptAnyCertificate;
184 GPALMailSettings.Credentials = credentials;
191 public DateTime?
Sent {
get;
private set; }
201 public Enums.MailFailure
Failure {
get;
internal set; }
205 public string Server {
get;
internal set; }
207 public int Port {
get;
internal set; }
231 private void Failed(Enums.MailFailure failure,
string message,
string server,
int port, Exception ex)
239 UserName = ResolvedUserName,
240 Occurred = DateTime.Now,
244 GPAL.PublishSimpleEvent(
null == ex ? Enums.GPALEventType.ERROR : Enums.GPALEventType.EXCEPTION,
245 message,
this, Enums.GPALObjectType.Other, ex);
257 private bool Reached(Action connect, Action authenticate,
string server,
int port)
266 catch (MailKit.Security.SslHandshakeException ex)
269 Failed(Enums.MailFailure.Certificate,
270 $
"[{server}] on port [{port}] offered a certificate this machine will not accept. .WithAcceptAnyCertificate() connects anyway",
277 Failed(Enums.MailFailure.Connection, $
"Could not reach [{server}] on port [{port}]", server, port, ex);
282 if (
true == retVal &&
false ==
string.IsNullOrEmpty(ResolvedUserName))
292 Failed(Enums.MailFailure.Authentication, $
"[{server}] refused the login for [{ResolvedUserName}]", server, port, ex);
313 private string ResolvedUserName
317 return null != GPALMailSettings.Credentials ? ((
Credentials)GPALMailSettings.Credentials).Username : GPALMailSettings.UserName;
321 private string ResolvedPassword
325 return null != GPALMailSettings.Credentials ? ((
Credentials)GPALMailSettings.Credentials).Password : GPALMailSettings.Password;
336 GPALMailSettings.UserName = userName;
346 GPALMailSettings.Password = password;
356 private MailboxAddress lastAddress =
null;
360 GPALMailSettings.FromEmailAddress = MailboxAddress.Parse(mailAddress);
361 lastAddress = GPALMailSettings.FromEmailAddress;
374 GPALMailSettings.ToEmailAddresses.Add(mailboxAddress);
375 lastAddress = mailboxAddress;
390 GPALMailSettings.CcEmailAddresses.Add(mailboxAddress);
391 lastAddress = mailboxAddress;
406 GPALMailSettings.BccEmailAddresses.Add(mailboxAddress);
407 lastAddress = mailboxAddress;
418 foreach (MailboxAddress mailboxAddress
in CreateEmailAddresses(emailParts))
420 GPALMailSettings.ToEmailAddresses.Add(mailboxAddress);
421 lastAddress = mailboxAddress;
433 foreach (MailboxAddress mailboxAddress
in CreateEmailAddresses(emailParts))
435 GPALMailSettings.CcEmailAddresses.Add(mailboxAddress);
436 lastAddress = mailboxAddress;
448 foreach (MailboxAddress mailboxAddress
in CreateEmailAddresses(emailParts))
450 GPALMailSettings.BccEmailAddresses.Add(mailboxAddress);
451 lastAddress = mailboxAddress;
466 foreach (
string filename
in attachmentFile.Filenames)
469 string directory = Path.GetDirectoryName(filename);
470 string filenamepart = Path.GetFileName(filename);
471 string[] matches = Directory.GetFiles(
false ==
string.IsNullOrEmpty(directory) ? directory :
@".\", filenamepart);
474 if (0 == matches.Length)
475 GPAL.
PublishSimpleEvent(Enums.GPALEventType.WARNING, $
"Nothing to attach at [{filename}]", attachmentFile, Enums.GPALObjectType.Other);
477 foreach (
string filename2
in matches)
480 GPALMailSettings.Attachments.Add(filename2);
484 GPAL.
PublishSimpleEvent(Enums.GPALEventType.EXCEPTION, $
"Unable to add file attachments [{filename2}].", attachmentFile, Enums.GPALObjectType.Other, ex);
489 GPAL.
PublishSimpleEvent(Enums.GPALEventType.EXCEPTION, $
"Unable to add file attachments [{attachmentFile.Filename}].", attachmentFile, Enums.GPALObjectType.Other, ex);
502 if (
true == IsHtml(body))
503 GPALMailSettings.BodyHTML = body;
505 GPALMailSettings.BodyText = body;
516 GPALMailSettings.Subject = subject;
530 if (
null == lastAddress)
531 GPAL.
PublishSimpleEvent(Enums.GPALEventType.WARNING, $
"No address to put the name [{name}] on. Name an address after adding it",
this, Enums.GPALObjectType.Other);
533 lastAddress.Name = name;
562 if (
null == GPALMailSettings.FromEmailAddress)
565 Failed(Enums.MailFailure.Configuration, $
"No from address to send with", GPALMailSettings.SMTPServerName, GPALMailSettings.SMTPPortNum,
null);
570 var message =
new MimeMessage();
571 message.From.Add(GPALMailSettings.FromEmailAddress);
573 if (
null != GPALMailSettings.ToEmailAddresses && 0 < GPALMailSettings.ToEmailAddresses.Count)
574 foreach (var to
in GPALMailSettings.ToEmailAddresses)
577 if (
null != GPALMailSettings.CcEmailAddresses && 0 < GPALMailSettings.CcEmailAddresses.Count)
578 foreach (var cc
in GPALMailSettings.CcEmailAddresses)
581 if (
null != GPALMailSettings.BccEmailAddresses && 0 < GPALMailSettings.BccEmailAddresses.Count)
582 foreach (var bcc
in GPALMailSettings.BccEmailAddresses)
583 message.Bcc.Add(bcc);
585 message.Subject = GPALMailSettings.Subject;
586 var bodyBuilder =
new BodyBuilder();
587 if (
null != GPALMailSettings.BodyHTML)
588 bodyBuilder.HtmlBody = GPALMailSettings.BodyHTML;
590 bodyBuilder.TextBody = GPALMailSettings.BodyText;
591 if (
null != GPALMailSettings.Attachments && 0 < GPALMailSettings.Attachments.Count)
593 foreach (var attachment
in GPALMailSettings.Attachments)
595 bodyBuilder.Attachments.Add(attachment);
598 message.Body = bodyBuilder.ToMessageBody();
600 using (var client =
new SmtpClient())
604 if (
true == GPALMailSettings.AcceptAnyCertificate)
607 $
"Connecting to [{GPALMailSettings.SMTPServerName}] without checking its certificate, because the workflow asked for that",
608 this, Enums.GPALObjectType.None);
610 client.ServerCertificateValidationCallback = (s, c, h, e) =>
true;
615 bool loggedIn = Reached(
616 () => client.Connect(GPALMailSettings.SMTPServerName, GPALMailSettings.SMTPPortNum, MailKit.Security.SecureSocketOptions.Auto),
617 () => client.Authenticate(ResolvedUserName, ResolvedPassword),
618 GPALMailSettings.SMTPServerName, GPALMailSettings.SMTPPortNum);
620 if (
true == loggedIn)
623 client.Send(message);
624 client.Disconnect(
true);
632 Failed(Enums.MailFailure.Transfer,
633 $
"[{GPALMailSettings.SMTPServerName}] accepted the login and then would not take the message",
634 GPALMailSettings.SMTPServerName, GPALMailSettings.SMTPPortNum, ex);
653 if (
false ==
string.IsNullOrWhiteSpace(GPALMailSettings.POPServerName))
655 else if (
false ==
string.IsNullOrWhiteSpace(GPALMailSettings.IMAPServerName))
658 Failed(Enums.MailFailure.Configuration, $
"No pop or imap server to read mail from",
null, 0,
null);
672 receivedEmails =
new List<ReceivedEmail>(GPALMailSettings.ReceivedEmails);
674 GPAL.
PublishSimpleEvent(Enums.GPALEventType.INFO, $
"Handing back [{receivedEmails.Count}] received emails.",
this, Enums.GPALObjectType.Other);
687 GPALMailSettings.UnreadOnly = unreadOnly;
699 GPALMailSettings.MaxMessages = maxMessages;
703 private void ReceiveOverPOP()
705 using (var client =
new Pop3Client())
707 () => client.Connect(GPALMailSettings.POPServerName, GPALMailSettings.POPPortNum, MailKit.Security.SecureSocketOptions.Auto),
708 () => client.Authenticate(ResolvedUserName, ResolvedPassword),
709 GPALMailSettings.POPServerName, GPALMailSettings.POPPortNum))
712 if (
true == GPALMailSettings.UnreadOnly)
713 GPAL.
PublishSimpleEvent(Enums.GPALEventType.WARNING, $
"POP cannot tell read from unread, so every message is being fetched from [{GPALMailSettings.POPServerName}]",
this, Enums.GPALObjectType.Other);
717 int first = Math.Max(0, client.Count - GPALMailSettings.MaxMessages);
719 for (
int i = first; i < client.Count; i++)
720 Collect(client.GetMessage(i),
null);
722 client.Disconnect(
true);
728 Failed(Enums.MailFailure.Transfer,
729 $
"[{GPALMailSettings.POPServerName}] stopped while the mailbox was being read",
730 GPALMailSettings.POPServerName, GPALMailSettings.POPPortNum, ex);
735 private void ReceiveOverIMAP()
737 using (var client =
new ImapClient())
739 () => client.Connect(GPALMailSettings.IMAPServerName, GPALMailSettings.IMAPPortNum,
true),
740 () => client.Authenticate(ResolvedUserName, ResolvedPassword),
741 GPALMailSettings.IMAPServerName, GPALMailSettings.IMAPPortNum))
744 var inbox = client.Inbox;
745 inbox.Open(MailKit.FolderAccess.ReadOnly);
748 IList<UniqueId> ids = inbox.Search(
true == GPALMailSettings.UnreadOnly
749 ? SearchQuery.NotSeen
752 int first = Math.Max(0, ids.Count - GPALMailSettings.MaxMessages);
754 for (
int i = first; i < ids.Count; i++)
755 Collect(inbox.GetMessage(ids[i]), ids[i]);
757 client.Disconnect(
true);
763 Failed(Enums.MailFailure.Transfer,
764 $
"[{GPALMailSettings.IMAPServerName}] stopped while the inbox was being read",
765 GPALMailSettings.IMAPServerName, GPALMailSettings.IMAPPortNum, ex);
777 ApplyFlag(GPALMailSettings.ReceivedEmails, MessageFlags.Seen,
false);
789 ApplyFlag(GPALMailSettings.ReceivedEmails, MessageFlags.Deleted,
true);
801 internal void ApplyFlag(IEnumerable<ReceivedEmail> messages, MessageFlags flag,
bool expunge)
803 List<UniqueId> uids = messages.Where(m =>
null != m?.Uid).Select(m => m.Uid.Value).ToList();
806 GPAL.
PublishSimpleEvent(Enums.GPALEventType.WARNING, $
"Nothing to flag: no message came from an IMAP Receive",
this, Enums.GPALObjectType.Other);
808 using (var client =
new ImapClient())
810 client.Connect(GPALMailSettings.IMAPServerName, GPALMailSettings.IMAPPortNum,
true);
811 client.Authenticate(ResolvedUserName, ResolvedPassword);
813 var inbox = client.Inbox;
814 inbox.Open(MailKit.FolderAccess.ReadWrite);
815 inbox.AddFlags(uids, flag,
true);
820 client.Disconnect(
true);
822 GPAL.
PublishSimpleEvent(Enums.GPALEventType.INFO, $
"Flagged [{uids.Count}] message(s) as [{flag}].",
this, Enums.GPALObjectType.Other);
826 private void Collect(MimeMessage message, UniqueId? uid)
828 ReceivedEmail received =
new ReceivedEmail
830 Subject = message.Subject,
831 From = message.From.ToString(),
833 Body = message.TextBody ?? message.HtmlBody,
834 HtmlBody = message.HtmlBody,
835 Received = message.Date.LocalDateTime,
836 MessageId = message.MessageId,
843 foreach (MimeEntity attachment
in message.Attachments)
845 received.
Attachments.Add(attachment.ContentDisposition?.FileName ?? attachment.ContentType?.Name);
846 received.AttachmentParts.Add(attachment);
849 GPALMailSettings.ReceivedEmails.Add(received);
861 foreach (
ReceivedEmail received
in GPALMailSettings.ReceivedEmails)
862 written += received.
SaveTo(directory).Count;
864 GPAL.
PublishSimpleEvent(Enums.GPALEventType.INFO, $
"Wrote [{written}] attachments to [{directory.Filename}].",
this, Enums.GPALObjectType.Other);
879 GPALMailSettings.ToEmailAddresses.Clear();
880 GPALMailSettings.CcEmailAddresses.Clear();
881 GPALMailSettings.BccEmailAddresses.Clear();
882 GPALMailSettings.Subject =
null;
883 GPALMailSettings.BodyText =
null;
884 GPALMailSettings.BodyHTML =
null;
885 GPALMailSettings.Attachments.Clear();
886 GPALMailSettings.ReceivedEmails.Clear();
897 if (mailSettingsFile !=
null)
898 MailSettingsFilePath = mailSettingsFile.Filename;
900 if (
true == System.IO.File.Exists(MailSettingsFilePath))
917 if (mailSettingsFile !=
null) MailSettingsFilePath = mailSettingsFile.Filenames[0];
932 string[] emailParts = input.Split(
new char[] {
',',
';',
'|' });
933 return CreateEmailAddresses(emailParts);
940 internal static List<MailboxAddress> CreateEmailAddresses(
string[] emailParts)
942 List<MailboxAddress> addresses =
new List<MailboxAddress>();
943 foreach (
string emailPart
in emailParts)
945 string email = emailPart.Trim();
946 if (!
string.IsNullOrEmpty(email))
949 MailboxAddress address = MailboxAddress.Parse(email);
950 addresses.Add(address);
962 static bool IsHtml(
string text)
966 if (text.TrimStart().StartsWith(
"<!DOCTYPE html>", StringComparison.OrdinalIgnoreCase))
968 else if (text.TrimStart().StartsWith(
"<?xml", StringComparison.OrdinalIgnoreCase))
972 string pattern =
@"<\s*([^ >]+)[^>]*>.*?<\s*/\s*\1\s*>";
975 return System.Text.RegularExpressions.Regex.IsMatch(text, pattern);
988 public string From {
get;
set; }
990 public string Body {
get;
set; }
999 internal UniqueId? Uid {
get;
set; }
1002 internal GPALMail Mailbox {
get;
set; }
1012 Mailbox.ApplyFlag(
new[] {
this }, MessageFlags.Seen,
false);
1023 Mailbox.ApplyFlag(
new[] {
this }, MessageFlags.Deleted,
true);
1030 internal List<MimeEntity> AttachmentParts {
get;
set; } =
new List<MimeEntity>();
1040 List<GPALFile> retVal =
new List<GPALFile>();
1042 if (0 < AttachmentParts.Count)
1043 Directory.CreateDirectory(directory.
Filename);
1045 foreach (MimeEntity attachment
in AttachmentParts)
1049 string filename = Path.GetFileName(attachment.ContentDisposition?.FileName ?? attachment.ContentType?.Name);
1050 string path = Path.Combine(directory.Filename, filename);
1052 using (FileStream stream = File.Create(path))
1053 if (attachment is MimePart part)
1054 part.Content.DecodeTo(stream);
1056 attachment.WriteTo(stream);
GPAL File object instantied with GPAL.File Used to load tokens into a GPALGrid [rows/columns].
string Filename
We have only one file, accessing it.
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
static IAllowConverterInput Converter
New GPAL Convertor.
static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject=null, Enums.GPALObjectType gPALObjectType=GPALObjectType.None, Exception ex=null)
Publish a message to either the information channel or exception channel (if exception passed in) Pub...
What went wrong on the last call, and enough about it to act on. Sent says whether the mail went....
string UserName
The account it signed in as, null where it had none to sign in with.
Enums.MailFailure Failure
Which way the call failed.
string Message
What the failure said.
DateTime Occurred
When it happened.
Exception Exception
The exception itself, for a caller that wants the stack.
string Server
The server it was talking to.
Provides a fluent API for configuring, sending, and receiving email via SMTP, IMAP,...
IAllowEmailEnvelopeAndActions Receive()
Connects to the configured IMAP server, authenticates with GPALMailSettings.UserName and GPALMailSett...
string IMAPServerName
The IMAP server mail is read from, null when a POP server is the one named.
IAllowEmailServerSettingsAndName WithSMTPPortNum(int portNum=465)
Sets the port number used to connect to the SMTP server.
IAllowEmailEnvelopeAndActions Receive(out List< ReceivedEmail > receivedEmails)
Reads the mail as Receive does and hands the messages over in one call.
IAllowNameEnvelopeAndActions WithToEmailAddress(string mailAddress)
Adds one or more "To" recipients, parsed from a single string that may contain multiple addresses del...
IAllowNameEnvelopeAndActions WithCcEmailAddress(string mailAddress)
Adds one or more "Cc" recipients, parsed from a single string that may contain multiple addresses del...
IAllowEmailServerSettingsAndName WithCredentials(ICredentials credentials)
The credential to authenticate with, from wherever GPAL got it: a vault, a file, or set by hand....
IAllowEmailEnvelopeAndActions SaveSettings(GPALFile mailSettingsFile=null)
Saves this instance's GPALMailSettings (server connection and envelope settings) to the given GPALFil...
IAllowEmailEnvelopeAndActions WithUnreadOnly(bool unreadOnly)
Limits Receive() to the messages the server still has as unread. Off by default, so a Receive with no...
IAllowNameEnvelopeAndActions WithBccEmailAddress(string[] emailParts)
Adds one or more "Bcc" recipients from an array of email address strings.
int SMTPPortNum
The port used to reach SMTPServerName.
int POPPortNum
The port used to reach POPServerName.
IAllowEmailServerSettingsAndName WithUserName(string userName)
Sets the username used to authenticate with the mail server(s).
IAllowNameEnvelopeAndActions WithCcEmailAddress(string[] emailParts)
Adds one or more "Cc" recipients from an array of email address strings.
IAllowEmailEnvelopeAndActions MarkAllAsRead()
Marks every message Receive() collected as read on the server, in one connection, so the next Receive...
DateTime? Sent
When the last Send went out, null when it did not.
MailError LastError
The last failure, or null when the last call did not fail. Cleared at the start of Send and Receive()...
IAllowEmailEnvelopeAndActions SaveTo(GPALFile directory)
Writes every attachment on every message Receive collected into the given directory,...
IAllowEmailServerSettingsAndName WithIMAPServerName(string serverName)
Sets the IMAP server hostname used for receiving mail, and forgets any POP server,...
string SMTPServerName
The SMTP server mail is sent through, as set by WithSMTPServerName or read from a settings file.
IGPALMail ToGPALObject()
Returns this instance cast to IGPALMail, allowing the fluent chain to be converted back to the root m...
string FromAddress
The address messages are sent from, as text, with the display name when there is one.
IAllowEmailEnvelopeAndActions WithName(string name)
Sets the display name on the address added just before this call, whether that was the from,...
IAllowEmailEnvelopeAndActions WithBody(string body)
Sets the message body, automatically detecting whether it is HTML or plain text via IsHtml(string) an...
IAllowEmailEnvelopeAndActions WithAttachment(GPALFile attachmentFile)
Adds the file(s) referenced by the given GPALFile as email attachments. Each filename in GPALFile....
IAllowEmailEnvelopeAndActions WithSubject(string subject)
Sets the subject line of the message.
string POPServerName
The POP server mail is read from, null when an IMAP server is the one named.
IAllowEmailServerSettingsAndName WithIMAPPortNum(int portNum=993)
Sets the port number used to connect to the IMAP server.
IAllowEmailEnvelopeAndActions Clear()
Resets the email envelope to a blank state: clears all recipient lists, subject, body text,...
IAllowEmailEnvelopeAndActions DeleteAll()
Deletes every message Receive() collected from the server, flagging and expunging them in one connect...
IAllowEmailServerSettingsAndName WithPassword(string password)
Sets the password used to authenticate with the mail server(s).
IAllowEmailEnvelopeAndActions Send()
Builds the message from the configured envelope (from/to/cc/bcc, subject, body, attachments) and send...
IAllowEmailServerSettingsAndName WithAcceptAnyCertificate(bool acceptAnyCertificate=true)
Send even when the server's certificate cannot be validated. Off unless a workflow says otherwise,...
IAllowEmailServerSettingsAndName WithPOPServerName(string serverName)
Sets the POP server hostname used for receiving mail, and forgets any IMAP server,...
IAllowEmailEnvelopeAndActions LoadSettings(GPALFile mailSettingsFile=null)
Loads this instance's GPALMailSettings from the given GPALFile via GPAL.Converter,...
IAllowEmailServerSettingsAndName WithPOPPortNum(int portNum=110)
Sets the port number used to connect to the POP server.
IAllowNameEnvelopeAndActions WithBccEmailAddress(string mailAddress)
Adds one or more "Bcc" recipients, parsed from a single string that may contain multiple addresses de...
IAllowEmailEnvelopeAndActions WithMaxMessages(int maxMessages)
Caps how many messages one Receive() fetches, taking the newest first. Every message by default,...
IAllowEmailServerSettingsAndName WithSMTPServerName(string serverName)
Sets the SMTP server hostname used for sending mail.
int IMAPPortNum
The port used to reach IMAPServerName.
static List< MailboxAddress > ParseEmailAddresses(string input)
Splits a single string containing one or more email addresses delimited by commas,...
IAllowNameEnvelopeAndActions WithToEmailAddress(string[] emailParts)
Adds one or more "To" recipients from an array of email address strings.
A simplified representation of a single email message retrieved by GPALMail.Receive.
ReceivedEmail Delete()
Deletes this message from the server, flagging it and expunging it in the one call....
List< GPALFile > SaveTo(GPALFile directory)
Writes this message's attachments into the given directory, creating it when it is not there,...
ReceivedEmail MarkAsRead()
Marks this message as read on the server, so the next Receive set to unread only leaves it behind....
string Subject
The message's subject line.
List< string > Attachments
The filename of each attachment on the message, so a workflow can tell what came with it....
string From
The message's "From" address, as a display string.
string MessageId
The message id the sender stamped on it, which is how the same message is recognised across runs.
string Body
The plain-text body of the message, falling back to the html body when the sender wrote no text part.
string HtmlBody
The html body of the message, null when the sender wrote none.
DateTime Received
When the message was sent, in local time.