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;
171 GPALMailSettings.Credentials = credentials;
193 private string ResolvedUserName
197 return null != GPALMailSettings.Credentials ? ((
Credentials)GPALMailSettings.Credentials).Username : GPALMailSettings.UserName;
201 private string ResolvedPassword
205 return null != GPALMailSettings.Credentials ? ((
Credentials)GPALMailSettings.Credentials).Password : GPALMailSettings.Password;
216 GPALMailSettings.UserName = userName;
226 GPALMailSettings.Password = password;
236 private MailboxAddress lastAddress =
null;
240 GPALMailSettings.FromEmailAddress = MailboxAddress.Parse(mailAddress);
241 lastAddress = GPALMailSettings.FromEmailAddress;
254 GPALMailSettings.ToEmailAddresses.Add(mailboxAddress);
255 lastAddress = mailboxAddress;
270 GPALMailSettings.CcEmailAddresses.Add(mailboxAddress);
271 lastAddress = mailboxAddress;
286 GPALMailSettings.BccEmailAddresses.Add(mailboxAddress);
287 lastAddress = mailboxAddress;
298 foreach (MailboxAddress mailboxAddress
in CreateEmailAddresses(emailParts))
300 GPALMailSettings.ToEmailAddresses.Add(mailboxAddress);
301 lastAddress = mailboxAddress;
313 foreach (MailboxAddress mailboxAddress
in CreateEmailAddresses(emailParts))
315 GPALMailSettings.CcEmailAddresses.Add(mailboxAddress);
316 lastAddress = mailboxAddress;
328 foreach (MailboxAddress mailboxAddress
in CreateEmailAddresses(emailParts))
330 GPALMailSettings.BccEmailAddresses.Add(mailboxAddress);
331 lastAddress = mailboxAddress;
346 foreach (
string filename
in attachmentFile.Filenames)
349 string directory = Path.GetDirectoryName(filename);
350 string filenamepart = Path.GetFileName(filename);
351 string[] matches = Directory.GetFiles(
false ==
string.IsNullOrEmpty(directory) ? directory :
@".\", filenamepart);
354 if (0 == matches.Length)
355 GPAL.
PublishSimpleEvent(Enums.GPALEventType.WARNING, $
"Nothing to attach at [{filename}]", attachmentFile, Enums.GPALObjectType.Other);
357 foreach (
string filename2
in matches)
360 GPALMailSettings.Attachments.Add(filename2);
364 GPAL.
PublishSimpleEvent(Enums.GPALEventType.EXCEPTION, $
"Unable to add file attachments [{filename2}].", attachmentFile, Enums.GPALObjectType.Other, ex);
369 GPAL.
PublishSimpleEvent(Enums.GPALEventType.EXCEPTION, $
"Unable to add file attachments [{attachmentFile.Filename}].", attachmentFile, Enums.GPALObjectType.Other, ex);
382 if (
true == IsHtml(body))
383 GPALMailSettings.BodyHTML = body;
385 GPALMailSettings.BodyText = body;
396 GPALMailSettings.Subject = subject;
410 if (
null == lastAddress)
411 GPAL.
PublishSimpleEvent(Enums.GPALEventType.WARNING, $
"No address to put the name [{name}] on. Name an address after adding it",
this, Enums.GPALObjectType.Other);
413 lastAddress.Name = name;
438 if (
null == GPALMailSettings.FromEmailAddress)
441 GPAL.
PublishSimpleEvent(Enums.GPALEventType.ERROR, $
"No from address to send with",
this, Enums.GPALObjectType.Other);
446 var message =
new MimeMessage();
447 message.From.Add(GPALMailSettings.FromEmailAddress);
449 if (
null != GPALMailSettings.ToEmailAddresses && 0 < GPALMailSettings.ToEmailAddresses.Count)
450 foreach (var to
in GPALMailSettings.ToEmailAddresses)
453 if (
null != GPALMailSettings.CcEmailAddresses && 0 < GPALMailSettings.CcEmailAddresses.Count)
454 foreach (var cc
in GPALMailSettings.CcEmailAddresses)
457 if (
null != GPALMailSettings.BccEmailAddresses && 0 < GPALMailSettings.BccEmailAddresses.Count)
458 foreach (var bcc
in GPALMailSettings.BccEmailAddresses)
459 message.Bcc.Add(bcc);
461 message.Subject = GPALMailSettings.Subject;
462 var bodyBuilder =
new BodyBuilder();
463 if (
null != GPALMailSettings.BodyHTML)
464 bodyBuilder.HtmlBody = GPALMailSettings.BodyHTML;
466 bodyBuilder.TextBody = GPALMailSettings.BodyText;
467 if (
null != GPALMailSettings.Attachments && 0 < GPALMailSettings.Attachments.Count)
469 foreach (var attachment
in GPALMailSettings.Attachments)
471 bodyBuilder.Attachments.Add(attachment);
474 message.Body = bodyBuilder.ToMessageBody();
476 using (var client =
new SmtpClient())
480 client.Connect(GPALMailSettings.SMTPServerName, GPALMailSettings.SMTPPortNum,
false);
484 client.ServerCertificateValidationCallback = (s, c, h, e) =>
true;
485 client.DeliveryStatusNotificationType = MailKit.Net.Smtp.DeliveryStatusNotificationType.Full;
486 client.SslProtocols = System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls12;
488 client.Connect(GPALMailSettings.SMTPServerName, GPALMailSettings.SMTPPortNum,
false);
492 if (
false ==
string.IsNullOrEmpty(ResolvedUserName))
493 client.Authenticate(ResolvedUserName, ResolvedPassword);
495 client.Send(message);
496 client.Disconnect(
true);
512 if (
false ==
string.IsNullOrWhiteSpace(GPALMailSettings.POPServerName))
514 else if (
false ==
string.IsNullOrWhiteSpace(GPALMailSettings.IMAPServerName))
517 GPAL.
PublishSimpleEvent(Enums.GPALEventType.ERROR, $
"No pop or imap server to read mail from",
this, Enums.GPALObjectType.Other);
531 receivedEmails =
new List<ReceivedEmail>(GPALMailSettings.ReceivedEmails);
533 GPAL.
PublishSimpleEvent(Enums.GPALEventType.INFO, $
"Handing back [{receivedEmails.Count}] received emails.",
this, Enums.GPALObjectType.Other);
546 GPALMailSettings.UnreadOnly = unreadOnly;
558 GPALMailSettings.MaxMessages = maxMessages;
562 private void ReceiveOverPOP()
564 using (var client =
new Pop3Client())
566 client.Connect(GPALMailSettings.POPServerName, GPALMailSettings.POPPortNum, MailKit.Security.SecureSocketOptions.Auto);
567 client.Authenticate(ResolvedUserName, ResolvedPassword);
570 if (
true == GPALMailSettings.UnreadOnly)
571 GPAL.
PublishSimpleEvent(Enums.GPALEventType.WARNING, $
"POP cannot tell read from unread, so every message is being fetched from [{GPALMailSettings.POPServerName}]",
this, Enums.GPALObjectType.Other);
573 int first = Math.Max(0, client.Count - GPALMailSettings.MaxMessages);
575 for (
int i = first; i < client.Count; i++)
576 Collect(client.GetMessage(i),
null);
578 client.Disconnect(
true);
582 private void ReceiveOverIMAP()
584 using (var client =
new ImapClient())
586 client.Connect(GPALMailSettings.IMAPServerName, GPALMailSettings.IMAPPortNum,
true);
587 client.Authenticate(ResolvedUserName, ResolvedPassword);
589 var inbox = client.Inbox;
590 inbox.Open(MailKit.FolderAccess.ReadOnly);
593 IList<UniqueId> ids = inbox.Search(
true == GPALMailSettings.UnreadOnly
594 ? SearchQuery.NotSeen
597 int first = Math.Max(0, ids.Count - GPALMailSettings.MaxMessages);
599 for (
int i = first; i < ids.Count; i++)
600 Collect(inbox.GetMessage(ids[i]), ids[i]);
602 client.Disconnect(
true);
614 ApplyFlag(GPALMailSettings.ReceivedEmails, MessageFlags.Seen,
false);
626 ApplyFlag(GPALMailSettings.ReceivedEmails, MessageFlags.Deleted,
true);
638 internal void ApplyFlag(IEnumerable<ReceivedEmail> messages, MessageFlags flag,
bool expunge)
640 List<UniqueId> uids = messages.Where(m =>
null != m?.Uid).Select(m => m.Uid.Value).ToList();
643 GPAL.
PublishSimpleEvent(Enums.GPALEventType.WARNING, $
"Nothing to flag: no message came from an IMAP Receive",
this, Enums.GPALObjectType.Other);
645 using (var client =
new ImapClient())
647 client.Connect(GPALMailSettings.IMAPServerName, GPALMailSettings.IMAPPortNum,
true);
648 client.Authenticate(ResolvedUserName, ResolvedPassword);
650 var inbox = client.Inbox;
651 inbox.Open(MailKit.FolderAccess.ReadWrite);
652 inbox.AddFlags(uids, flag,
true);
657 client.Disconnect(
true);
659 GPAL.
PublishSimpleEvent(Enums.GPALEventType.INFO, $
"Flagged [{uids.Count}] message(s) as [{flag}].",
this, Enums.GPALObjectType.Other);
663 private void Collect(MimeMessage message, UniqueId? uid)
665 ReceivedEmail received =
new ReceivedEmail
667 Subject = message.Subject,
668 From = message.From.ToString(),
670 Body = message.TextBody ?? message.HtmlBody,
671 HtmlBody = message.HtmlBody,
672 Received = message.Date.LocalDateTime,
673 MessageId = message.MessageId,
680 foreach (MimeEntity attachment
in message.Attachments)
682 received.
Attachments.Add(attachment.ContentDisposition?.FileName ?? attachment.ContentType?.Name);
683 received.AttachmentParts.Add(attachment);
686 GPALMailSettings.ReceivedEmails.Add(received);
698 foreach (
ReceivedEmail received
in GPALMailSettings.ReceivedEmails)
699 written += received.
SaveTo(directory).Count;
701 GPAL.
PublishSimpleEvent(Enums.GPALEventType.INFO, $
"Wrote [{written}] attachments to [{directory.Filename}].",
this, Enums.GPALObjectType.Other);
716 GPALMailSettings.ToEmailAddresses.Clear();
717 GPALMailSettings.CcEmailAddresses.Clear();
718 GPALMailSettings.BccEmailAddresses.Clear();
719 GPALMailSettings.Subject =
null;
720 GPALMailSettings.BodyText =
null;
721 GPALMailSettings.Attachments.Clear();
722 GPALMailSettings.ReceivedEmails.Clear();
733 if (mailSettingsFile !=
null)
734 MailSettingsFilePath = mailSettingsFile.Filename;
736 if (
true == System.IO.File.Exists(MailSettingsFilePath))
753 if (mailSettingsFile !=
null) MailSettingsFilePath = mailSettingsFile.Filenames[0];
768 string[] emailParts = input.Split(
new char[] {
',',
';',
'|' });
769 return CreateEmailAddresses(emailParts);
776 internal static List<MailboxAddress> CreateEmailAddresses(
string[] emailParts)
778 List<MailboxAddress> addresses =
new List<MailboxAddress>();
779 foreach (
string emailPart
in emailParts)
781 string email = emailPart.Trim();
782 if (!
string.IsNullOrEmpty(email))
785 MailboxAddress address = MailboxAddress.Parse(email);
786 addresses.Add(address);
798 static bool IsHtml(
string text)
802 if (text.TrimStart().StartsWith(
"<!DOCTYPE html>", StringComparison.OrdinalIgnoreCase))
804 else if (text.TrimStart().StartsWith(
"<?xml", StringComparison.OrdinalIgnoreCase))
808 string pattern =
@"<\s*([^ >]+)[^>]*>.*?<\s*/\s*\1\s*>";
811 return System.Text.RegularExpressions.Regex.IsMatch(text, pattern);
824 public string From {
get;
set; }
826 public string Body {
get;
set; }
835 internal UniqueId? Uid {
get;
set; }
838 internal GPALMail Mailbox {
get;
set; }
848 Mailbox.ApplyFlag(
new[] {
this }, MessageFlags.Seen,
false);
859 Mailbox.ApplyFlag(
new[] {
this }, MessageFlags.Deleted,
true);
863 public List<string>
Attachments {
get;
set; } =
new List<string>();
866 internal List<MimeEntity> AttachmentParts {
get;
set; } =
new List<MimeEntity>();
876 List<GPALFile> retVal =
new List<GPALFile>();
878 if (0 < AttachmentParts.Count)
879 Directory.CreateDirectory(directory.
Filename);
881 foreach (MimeEntity attachment
in AttachmentParts)
885 string filename = Path.GetFileName(attachment.ContentDisposition?.FileName ?? attachment.ContentType?.Name);
886 string path = Path.Combine(directory.Filename, filename);
888 using (FileStream stream = File.Create(path))
889 if (attachment is MimePart part)
890 part.Content.DecodeTo(stream);
892 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...
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...
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 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.