GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
GPALMail.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using System;
18using System.Collections.Generic;
19using System.Linq;
20using System.Text;
21using System.Threading.Tasks;
23
24namespace GenerallyPositive
25{
26 using System.Collections.Generic;
27 using System.IO;
28 using MailKit.Net.Imap;
29 using MailKit.Net.Pop3;
30 using MailKit;
31 using MailKit.Net.Smtp;
32 using MailKit.Search;
33 using MimeKit;
34
54 public class GPALMail : IGPALMail
55 {
56 private GPALMailSettings gPALMailSettings;
57 private static string MailSettingsFilePath { get; set; } = "./GPALMail.yaml";
62 private GPALMailSettings GPALMailSettings
63 {
64 get
65 {
66 return gPALMailSettings;
67 }
68 set
69 {
70 gPALMailSettings = value;
71 }
72 }
77 internal GPALMail()
78 {
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>();
86 }
93 {
94 return this;
95 }
96
103 {
104 GPALMailSettings.IMAPServerName = serverName;
105 GPALMailSettings.POPServerName = null;
106 return this;
107 }
108
116 {
117 GPALMailSettings.POPServerName = serverName;
118 GPALMailSettings.IMAPServerName = null;
119 return this;
120 }
121
128 {
129 GPALMailSettings.SMTPServerName = serverName;
130 return this;
131 }
132
139 {
140 GPALMailSettings.IMAPPortNum = portNum;
141 return this;
142 }
143
149 {
150 GPALMailSettings.POPPortNum = portNum;
151 return this;
152 }
153
159 {
160 GPALMailSettings.SMTPPortNum = portNum;
161 return this;
162 }
163
171 public IAllowEmailServerSettingsAndName WithAcceptAnyCertificate(bool acceptAnyCertificate = true)
172 {
173 GPALMailSettings.AcceptAnyCertificate = acceptAnyCertificate;
174 return this;
175 }
176
183 {
184 GPALMailSettings.Credentials = credentials;
185 return this;
186 }
187
188 // what a settings file holds, readable so a form can show it. everything else about a message is the
189 // envelope, and the login is the credential's business
191 public DateTime? Sent { get; private set; }
192
198 public class MailError
199 {
201 public Enums.MailFailure Failure { get; internal set; }
203 public string Message { get; internal set; }
205 public string Server { get; internal set; }
207 public int Port { get; internal set; }
209 public string UserName { get; internal set; }
211 public DateTime Occurred { get; internal set; }
213 public Exception Exception { get; internal set; }
214 }
215
220 public MailError LastError { get; private set; }
221
231 private void Failed(Enums.MailFailure failure, string message, string server, int port, Exception ex)
232 {
233 LastError = new MailError
234 {
235 Failure = failure,
236 Message = message,
237 Server = server,
238 Port = port,
239 UserName = ResolvedUserName,
240 Occurred = DateTime.Now,
241 Exception = ex
242 };
243
244 GPAL.PublishSimpleEvent(null == ex ? Enums.GPALEventType.ERROR : Enums.GPALEventType.EXCEPTION,
245 message, this, Enums.GPALObjectType.Other, ex);
246 }
247
257 private bool Reached(Action connect, Action authenticate, string server, int port)
258 {
259 bool retVal = false;
260
261 try
262 {
263 connect();
264 retVal = true;
265 }
266 catch (MailKit.Security.SslHandshakeException ex)
267 {
268 // the one failure the setting answers, so it is the only one named for it
269 Failed(Enums.MailFailure.Certificate,
270 $"[{server}] on port [{port}] offered a certificate this machine will not accept. .WithAcceptAnyCertificate() connects anyway",
271 server, port, ex);
272 }
273 catch (Exception ex)
274 {
275 // a host that never answers, a port nothing listens on, a refused connection. A mail server
276 // being unreachable is the workflow's business rather than the end of the run
277 Failed(Enums.MailFailure.Connection, $"Could not reach [{server}] on port [{port}]", server, port, ex);
278 }
279
280 // a server that wants to know who is sending. nothing to authenticate with means nothing to do,
281 // which is how an open relay on a local port has always worked here
282 if (true == retVal && false == string.IsNullOrEmpty(ResolvedUserName))
283 try
284 {
285 authenticate();
286 }
287 catch (Exception ex)
288 {
289 // a server refusing a login is the workflow's business, not the end of the run
290 retVal = false;
291
292 Failed(Enums.MailFailure.Authentication, $"[{server}] refused the login for [{ResolvedUserName}]", server, port, ex);
293 }
294
295 return retVal;
296 }
298 public string SMTPServerName => GPALMailSettings.SMTPServerName;
300 public int SMTPPortNum => GPALMailSettings.SMTPPortNum;
302 public string IMAPServerName => GPALMailSettings.IMAPServerName;
304 public int IMAPPortNum => GPALMailSettings.IMAPPortNum;
306 public string POPServerName => GPALMailSettings.POPServerName;
308 public int POPPortNum => GPALMailSettings.POPPortNum;
310 public string FromAddress => GPALMailSettings.FromAddress;
311
312 // the credential if there is one, otherwise whatever was set by hand
313 private string ResolvedUserName
314 {
315 get
316 {
317 return null != GPALMailSettings.Credentials ? ((Credentials)GPALMailSettings.Credentials).Username : GPALMailSettings.UserName;
318 }
319 }
320
321 private string ResolvedPassword
322 {
323 get
324 {
325 return null != GPALMailSettings.Credentials ? ((Credentials)GPALMailSettings.Credentials).Password : GPALMailSettings.Password;
326 }
327 }
328
335 {
336 GPALMailSettings.UserName = userName;
337 return this;
338 }
339
345 {
346 GPALMailSettings.Password = password;
347 return this;
348 }
349
355 // whichever address was added last, so .WithName knows which one it is naming
356 private MailboxAddress lastAddress = null;
357
358 public IAllowNameEnvelopeAndActions WithFromEmailAddress(string mailAddress)
359 {
360 GPALMailSettings.FromEmailAddress = MailboxAddress.Parse(mailAddress);
361 lastAddress = GPALMailSettings.FromEmailAddress;
362 return this;
363 }
371 {
372 foreach (MailboxAddress mailboxAddress in ParseEmailAddresses(mailAddress))
373 {
374 GPALMailSettings.ToEmailAddresses.Add(mailboxAddress);
375 lastAddress = mailboxAddress;
376 }
377 return this;
378 }
379
387 {
388 foreach (MailboxAddress mailboxAddress in ParseEmailAddresses(mailAddress))
389 {
390 GPALMailSettings.CcEmailAddresses.Add(mailboxAddress);
391 lastAddress = mailboxAddress;
392 }
393 return this;
394 }
395
403 {
404 foreach (MailboxAddress mailboxAddress in ParseEmailAddresses(mailAddress))
405 {
406 GPALMailSettings.BccEmailAddresses.Add(mailboxAddress);
407 lastAddress = mailboxAddress;
408 }
409 return this;
410 }
411
417 {
418 foreach (MailboxAddress mailboxAddress in CreateEmailAddresses(emailParts))
419 {
420 GPALMailSettings.ToEmailAddresses.Add(mailboxAddress);
421 lastAddress = mailboxAddress;
422 }
423 return this;
424 }
425
432 {
433 foreach (MailboxAddress mailboxAddress in CreateEmailAddresses(emailParts))
434 {
435 GPALMailSettings.CcEmailAddresses.Add(mailboxAddress);
436 lastAddress = mailboxAddress;
437 }
438 return this;
439 }
440
447 {
448 foreach (MailboxAddress mailboxAddress in CreateEmailAddresses(emailParts))
449 {
450 GPALMailSettings.BccEmailAddresses.Add(mailboxAddress);
451 lastAddress = mailboxAddress;
452 }
453 return this;
454 }
455
465 {
466 foreach (string filename in attachmentFile.Filenames)
467 try
468 {
469 string directory = Path.GetDirectoryName(filename);
470 string filenamepart = Path.GetFileName(filename);
471 string[] matches = Directory.GetFiles(false == string.IsNullOrEmpty(directory) ? directory : @".\", filenamepart);
472
473 // said out loud, because a name that matches no file attaches nothing and the message still sends
474 if (0 == matches.Length)
475 GPAL.PublishSimpleEvent(Enums.GPALEventType.WARNING, $"Nothing to attach at [{filename}]", attachmentFile, Enums.GPALObjectType.Other);
476
477 foreach (string filename2 in matches)
478 try
479 {
480 GPALMailSettings.Attachments.Add(filename2);
481 }
482 catch (Exception ex)
483 {
484 GPAL.PublishSimpleEvent(Enums.GPALEventType.EXCEPTION, $"Unable to add file attachments [{filename2}].", attachmentFile, Enums.GPALObjectType.Other, ex);
485 }
486 }
487 catch (Exception ex)
488 {
489 GPAL.PublishSimpleEvent(Enums.GPALEventType.EXCEPTION, $"Unable to add file attachments [{attachmentFile.Filename}].", attachmentFile, Enums.GPALObjectType.Other, ex);
490 }
491 return this;
492 }
493
501 {
502 if (true == IsHtml(body))
503 GPALMailSettings.BodyHTML = body;
504 else
505 GPALMailSettings.BodyText = body;
506 return this;
507 }
508
515 {
516 GPALMailSettings.Subject = subject;
517 return this;
518 }
519
527 {
528 // the address this follows, whether that was the from, a to, a cc or a bcc. named after the fact
529 // rather than parsed out of the address, so "Dead Mike <mike@x.com>" is not the only way to say it
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);
532 else
533 lastAddress.Name = name;
534
535 return this;
536 }
537
557 {
558 // nothing has been sent until something is, and nothing has failed until it does
559 Sent = null;
560 LastError = null;
561
562 if (null == GPALMailSettings.FromEmailAddress)
563 {
564 // said here rather than thrown from inside MimeKit as a null argument called "address"
565 Failed(Enums.MailFailure.Configuration, $"No from address to send with", GPALMailSettings.SMTPServerName, GPALMailSettings.SMTPPortNum, null);
566
567 return this;
568 }
569
570 var message = new MimeMessage();
571 message.From.Add(GPALMailSettings.FromEmailAddress);
572
573 if (null != GPALMailSettings.ToEmailAddresses && 0 < GPALMailSettings.ToEmailAddresses.Count)
574 foreach (var to in GPALMailSettings.ToEmailAddresses)
575 message.To.Add(to);
576
577 if (null != GPALMailSettings.CcEmailAddresses && 0 < GPALMailSettings.CcEmailAddresses.Count)
578 foreach (var cc in GPALMailSettings.CcEmailAddresses)
579 message.Cc.Add(cc);
580
581 if (null != GPALMailSettings.BccEmailAddresses && 0 < GPALMailSettings.BccEmailAddresses.Count)
582 foreach (var bcc in GPALMailSettings.BccEmailAddresses)
583 message.Bcc.Add(bcc);
584
585 message.Subject = GPALMailSettings.Subject;
586 var bodyBuilder = new BodyBuilder();
587 if (null != GPALMailSettings.BodyHTML)
588 bodyBuilder.HtmlBody = GPALMailSettings.BodyHTML;
589 else
590 bodyBuilder.TextBody = GPALMailSettings.BodyText;
591 if (null != GPALMailSettings.Attachments && 0 < GPALMailSettings.Attachments.Count)
592 {
593 foreach (var attachment in GPALMailSettings.Attachments)
594 {
595 bodyBuilder.Attachments.Add(attachment);
596 }
597 }
598 message.Body = bodyBuilder.ToMessageBody();
599
600 using (var client = new SmtpClient())
601 {
602 // said before the connection rather than worked out from a failure. The workflow has already
603 // decided whether it checks the certificate, so there is nothing to find out by failing first
604 if (true == GPALMailSettings.AcceptAnyCertificate)
605 {
606 GPAL.PublishSimpleEvent(Enums.GPALEventType.CAUTION,
607 $"Connecting to [{GPALMailSettings.SMTPServerName}] without checking its certificate, because the workflow asked for that",
608 this, Enums.GPALObjectType.None);
609
610 client.ServerCertificateValidationCallback = (s, c, h, e) => true;
611 }
612
613 // Auto picks by port the way POP already does: STARTTLS on 587, implicit TLS on 465. Passing
614 // false meant STARTTLS whatever the port, so the documented 465 default could never connect
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);
619
620 if (true == loggedIn)
621 try
622 {
623 client.Send(message);
624 client.Disconnect(true);
625
626 Sent = DateTime.Now;
627 }
628 catch (Exception ex)
629 {
630 // the server took the connection and the login and then would not take the message. A
631 // rejected recipient and a mailbox over quota both land here
632 Failed(Enums.MailFailure.Transfer,
633 $"[{GPALMailSettings.SMTPServerName}] accepted the login and then would not take the message",
634 GPALMailSettings.SMTPServerName, GPALMailSettings.SMTPPortNum, ex);
635 }
636 }
637
638 return this;
639 }
640
648 {
649 LastError = null;
650
651 // whichever was configured. a pop server named means pop, because naming one and having it ignored
652 // is worse than either
653 if (false == string.IsNullOrWhiteSpace(GPALMailSettings.POPServerName))
654 ReceiveOverPOP();
655 else if (false == string.IsNullOrWhiteSpace(GPALMailSettings.IMAPServerName))
656 ReceiveOverIMAP();
657 else
658 Failed(Enums.MailFailure.Configuration, $"No pop or imap server to read mail from", null, 0, null);
659
660 return this;
661 }
662
668 public IAllowEmailEnvelopeAndActions Receive(out List<ReceivedEmail> receivedEmails)
669 {
670 Receive();
671
672 receivedEmails = new List<ReceivedEmail>(GPALMailSettings.ReceivedEmails);
673
674 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $"Handing back [{receivedEmails.Count}] received emails.", this, Enums.GPALObjectType.Other);
675
676 return this;
677 }
678
686 {
687 GPALMailSettings.UnreadOnly = unreadOnly;
688 return this;
689 }
690
698 {
699 GPALMailSettings.MaxMessages = maxMessages;
700 return this;
701 }
702
703 private void ReceiveOverPOP()
704 {
705 using (var client = new Pop3Client())
706 if (true == Reached(
707 () => client.Connect(GPALMailSettings.POPServerName, GPALMailSettings.POPPortNum, MailKit.Security.SecureSocketOptions.Auto),
708 () => client.Authenticate(ResolvedUserName, ResolvedPassword),
709 GPALMailSettings.POPServerName, GPALMailSettings.POPPortNum))
710 {
711 // pop has no notion of a read message, so unread-only cannot be honoured here
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);
714
715 try
716 {
717 int first = Math.Max(0, client.Count - GPALMailSettings.MaxMessages);
718
719 for (int i = first; i < client.Count; i++)
720 Collect(client.GetMessage(i), null);
721
722 client.Disconnect(true);
723 }
724 catch (Exception ex)
725 {
726 // signed in and then the mailbox would not read. whatever was collected before it stopped
727 // is kept, because a workflow can work with part of a mailbox
728 Failed(Enums.MailFailure.Transfer,
729 $"[{GPALMailSettings.POPServerName}] stopped while the mailbox was being read",
730 GPALMailSettings.POPServerName, GPALMailSettings.POPPortNum, ex);
731 }
732 }
733 }
734
735 private void ReceiveOverIMAP()
736 {
737 using (var client = new ImapClient())
738 if (true == Reached(
739 () => client.Connect(GPALMailSettings.IMAPServerName, GPALMailSettings.IMAPPortNum, true),
740 () => client.Authenticate(ResolvedUserName, ResolvedPassword),
741 GPALMailSettings.IMAPServerName, GPALMailSettings.IMAPPortNum))
742 try
743 {
744 var inbox = client.Inbox;
745 inbox.Open(MailKit.FolderAccess.ReadOnly);
746
747 // ask the server which messages qualify rather than dragging the mailbox down to find out
748 IList<UniqueId> ids = inbox.Search(true == GPALMailSettings.UnreadOnly
749 ? SearchQuery.NotSeen
750 : SearchQuery.All);
751
752 int first = Math.Max(0, ids.Count - GPALMailSettings.MaxMessages);
753
754 for (int i = first; i < ids.Count; i++)
755 Collect(inbox.GetMessage(ids[i]), ids[i]);
756
757 client.Disconnect(true);
758 }
759 catch (Exception ex)
760 {
761 // signed in and then the inbox would not read. whatever was collected before it stopped
762 // is kept, because a workflow can work with part of a mailbox
763 Failed(Enums.MailFailure.Transfer,
764 $"[{GPALMailSettings.IMAPServerName}] stopped while the inbox was being read",
765 GPALMailSettings.IMAPServerName, GPALMailSettings.IMAPPortNum, ex);
766 }
767 }
768
776 {
777 ApplyFlag(GPALMailSettings.ReceivedEmails, MessageFlags.Seen, false);
778 return this;
779 }
780
788 {
789 ApplyFlag(GPALMailSettings.ReceivedEmails, MessageFlags.Deleted, true);
790 return this;
791 }
792
801 internal void ApplyFlag(IEnumerable<ReceivedEmail> messages, MessageFlags flag, bool expunge)
802 {
803 List<UniqueId> uids = messages.Where(m => null != m?.Uid).Select(m => m.Uid.Value).ToList();
804
805 if (0 == uids.Count)
806 GPAL.PublishSimpleEvent(Enums.GPALEventType.WARNING, $"Nothing to flag: no message came from an IMAP Receive", this, Enums.GPALObjectType.Other);
807 else
808 using (var client = new ImapClient())
809 {
810 client.Connect(GPALMailSettings.IMAPServerName, GPALMailSettings.IMAPPortNum, true);
811 client.Authenticate(ResolvedUserName, ResolvedPassword);
812
813 var inbox = client.Inbox;
814 inbox.Open(MailKit.FolderAccess.ReadWrite);
815 inbox.AddFlags(uids, flag, true);
816
817 if (true == expunge)
818 inbox.Expunge(uids);
819
820 client.Disconnect(true);
821
822 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $"Flagged [{uids.Count}] message(s) as [{flag}].", this, Enums.GPALObjectType.Other);
823 }
824 }
825
826 private void Collect(MimeMessage message, UniqueId? uid)
827 {
828 ReceivedEmail received = new ReceivedEmail
829 {
830 Subject = message.Subject,
831 From = message.From.ToString(),
832 // transactional mail is often html with no text part at all, and an empty body is no use
833 Body = message.TextBody ?? message.HtmlBody,
834 HtmlBody = message.HtmlBody,
835 Received = message.Date.LocalDateTime,
836 MessageId = message.MessageId,
837 Uid = uid,
838 Mailbox = this
839 };
840
841 // the name each attachment arrived under, and the part itself, so the file can be written later
842 // by whoever wants it and nothing is written by the act of reading mail
843 foreach (MimeEntity attachment in message.Attachments)
844 {
845 received.Attachments.Add(attachment.ContentDisposition?.FileName ?? attachment.ContentType?.Name);
846 received.AttachmentParts.Add(attachment);
847 }
848
849 GPALMailSettings.ReceivedEmails.Add(received);
850 }
858 {
859 int written = 0;
860
861 foreach (ReceivedEmail received in GPALMailSettings.ReceivedEmails)
862 written += received.SaveTo(directory).Count;
863
864 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $"Wrote [{written}] attachments to [{directory.Filename}].", this, Enums.GPALObjectType.Other);
865
866 return this;
867 }
868
876 {
877 // the from address stays: it is who you send as, it is saved with the server settings, and every
878 // message after this one would have to say it again
879 GPALMailSettings.ToEmailAddresses.Clear();
880 GPALMailSettings.CcEmailAddresses.Clear();
881 GPALMailSettings.BccEmailAddresses.Clear();
882 GPALMailSettings.Subject = null;
883 GPALMailSettings.BodyText = null;
884 GPALMailSettings.BodyHTML = null; // Send prefers this one, so leaving it made every later message re-send the last html body
885 GPALMailSettings.Attachments.Clear();
886 GPALMailSettings.ReceivedEmails.Clear();
887 return this;
888 }
889
896 {
897 if (mailSettingsFile != null)
898 MailSettingsFilePath = mailSettingsFile.Filename;
899
900 if (true == System.IO.File.Exists(MailSettingsFilePath))
901 GPAL.Converter.WithInput((GPALFile)MailSettingsFilePath).SaveTo(ref gPALMailSettings);
902 else
903 // nothing to load, so leave a file with the settings in it to fill in. a workflow that sets them
904 // itself and saves overwrites this with the real thing
905 SaveSettings();
906
907 return this;
908 }
909
916 {
917 if (mailSettingsFile != null) MailSettingsFilePath = mailSettingsFile.Filenames[0];
918 GPAL.Converter.WithInput(gPALMailSettings).SaveTo((GPALFile)MailSettingsFilePath);
919 return this;
920 }
921 #region HELPERS
928 public static List<MailboxAddress> ParseEmailAddresses(string input)
929 {
930
931 // Split the input string based on delimiters
932 string[] emailParts = input.Split(new char[] { ',', ';', '|' });
933 return CreateEmailAddresses(emailParts);
934 }
935
940 internal static List<MailboxAddress> CreateEmailAddresses(string[] emailParts)
941 {
942 List<MailboxAddress> addresses = new List<MailboxAddress>();
943 foreach (string emailPart in emailParts)
944 {
945 string email = emailPart.Trim();
946 if (!string.IsNullOrEmpty(email))
947 {
948 // Create a MailboxAddress object
949 MailboxAddress address = MailboxAddress.Parse(email);
950 addresses.Add(address);
951 }
952 }
953 return addresses;
954 }
962 static bool IsHtml(string text)
963 {
964 //
965 // Check if the text starts with an XML declaration
966 if (text.TrimStart().StartsWith("<!DOCTYPE html>", StringComparison.OrdinalIgnoreCase))
967 return true;
968 else if (text.TrimStart().StartsWith("<?xml", StringComparison.OrdinalIgnoreCase))
969 return false;
970
971 // Define a regular expression pattern to match HTML tags
972 string pattern = @"<\s*([^ >]+)[^>]*>.*?<\s*/\s*\1\s*>";
973
974 // Use Regex to search for HTML tags in the text
975 return System.Text.RegularExpressions.Regex.IsMatch(text, pattern);
976 }
977
978 #endregion HELPERS
979 }
980
983 public class ReceivedEmail
984 {
986 public string Subject { get; set; }
988 public string From { get; set; }
990 public string Body { get; set; }
992 public string HtmlBody { get; set; }
994 public DateTime Received { get; set; }
996 public string MessageId { get; set; }
997
998 // the imap identity of the message, so MarkAsRead knows which one to flag. null over pop
999 internal UniqueId? Uid { get; set; }
1000
1001 // the mail object that read this message, which is who still holds the server and the login
1002 internal GPALMail Mailbox { get; set; }
1003
1011 {
1012 Mailbox.ApplyFlag(new[] { this }, MessageFlags.Seen, false);
1013 return this;
1014 }
1015
1022 {
1023 Mailbox.ApplyFlag(new[] { this }, MessageFlags.Deleted, true);
1024 return this;
1025 }
1026
1027 public List<string> Attachments { get; set; } = new List<string>();
1028
1029 // the attachments themselves, held until someone asks for them on disk
1030 internal List<MimeEntity> AttachmentParts { get; set; } = new List<MimeEntity>();
1031
1038 public List<GPALFile> SaveTo(GPALFile directory)
1039 {
1040 List<GPALFile> retVal = new List<GPALFile>();
1041
1042 if (0 < AttachmentParts.Count)
1043 Directory.CreateDirectory(directory.Filename);
1044
1045 foreach (MimeEntity attachment in AttachmentParts)
1046 {
1047 // the name the sender chose, taken apart so nothing but a filename is used and a name like
1048 // ..\..\startup.cmd cannot write outside the directory that was asked for
1049 string filename = Path.GetFileName(attachment.ContentDisposition?.FileName ?? attachment.ContentType?.Name);
1050 string path = Path.Combine(directory.Filename, filename);
1051
1052 using (FileStream stream = File.Create(path))
1053 if (attachment is MimePart part)
1054 part.Content.DecodeTo(stream);
1055 else
1056 attachment.WriteTo(stream);
1057
1058 retVal.Add(path);
1059 }
1060
1061 return retVal;
1062 }
1063 }
1064}
1065
GPAL File object instantied with GPAL.File Used to load tokens into a GPALGrid [rows/columns].
Definition GPALFile.cs:36
string Filename
We have only one file, accessing it.
Definition GPALFile.cs:468
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
Definition GPAL.cs:49
static IAllowConverterInput Converter
New GPAL Convertor.
Definition GPAL.cs:571
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...
Definition GPAL.cs:2568
What went wrong on the last call, and enough about it to act on. Sent says whether the mail went....
Definition GPALMail.cs:199
string UserName
The account it signed in as, null where it had none to sign in with.
Definition GPALMail.cs:209
Enums.MailFailure Failure
Which way the call failed.
Definition GPALMail.cs:201
string Message
What the failure said.
Definition GPALMail.cs:203
DateTime Occurred
When it happened.
Definition GPALMail.cs:211
Exception Exception
The exception itself, for a caller that wants the stack.
Definition GPALMail.cs:213
string Server
The server it was talking to.
Definition GPALMail.cs:205
Provides a fluent API for configuring, sending, and receiving email via SMTP, IMAP,...
Definition GPALMail.cs:55
IAllowEmailEnvelopeAndActions Receive()
Connects to the configured IMAP server, authenticates with GPALMailSettings.UserName and GPALMailSett...
Definition GPALMail.cs:647
string IMAPServerName
The IMAP server mail is read from, null when a POP server is the one named.
Definition GPALMail.cs:302
IAllowEmailServerSettingsAndName WithSMTPPortNum(int portNum=465)
Sets the port number used to connect to the SMTP server.
Definition GPALMail.cs:158
IAllowEmailEnvelopeAndActions Receive(out List< ReceivedEmail > receivedEmails)
Reads the mail as Receive does and hands the messages over in one call.
Definition GPALMail.cs:668
IAllowNameEnvelopeAndActions WithToEmailAddress(string mailAddress)
Adds one or more "To" recipients, parsed from a single string that may contain multiple addresses del...
Definition GPALMail.cs:370
IAllowNameEnvelopeAndActions WithCcEmailAddress(string mailAddress)
Adds one or more "Cc" recipients, parsed from a single string that may contain multiple addresses del...
Definition GPALMail.cs:386
IAllowEmailServerSettingsAndName WithCredentials(ICredentials credentials)
The credential to authenticate with, from wherever GPAL got it: a vault, a file, or set by hand....
Definition GPALMail.cs:182
IAllowEmailEnvelopeAndActions SaveSettings(GPALFile mailSettingsFile=null)
Saves this instance's GPALMailSettings (server connection and envelope settings) to the given GPALFil...
Definition GPALMail.cs:915
IAllowEmailEnvelopeAndActions WithUnreadOnly(bool unreadOnly)
Limits Receive() to the messages the server still has as unread. Off by default, so a Receive with no...
Definition GPALMail.cs:685
IAllowNameEnvelopeAndActions WithBccEmailAddress(string[] emailParts)
Adds one or more "Bcc" recipients from an array of email address strings.
Definition GPALMail.cs:446
int SMTPPortNum
The port used to reach SMTPServerName.
Definition GPALMail.cs:300
int POPPortNum
The port used to reach POPServerName.
Definition GPALMail.cs:308
IAllowEmailServerSettingsAndName WithUserName(string userName)
Sets the username used to authenticate with the mail server(s).
Definition GPALMail.cs:334
IAllowNameEnvelopeAndActions WithCcEmailAddress(string[] emailParts)
Adds one or more "Cc" recipients from an array of email address strings.
Definition GPALMail.cs:431
IAllowEmailEnvelopeAndActions MarkAllAsRead()
Marks every message Receive() collected as read on the server, in one connection, so the next Receive...
Definition GPALMail.cs:775
DateTime? Sent
When the last Send went out, null when it did not.
Definition GPALMail.cs:191
MailError LastError
The last failure, or null when the last call did not fail. Cleared at the start of Send and Receive()...
Definition GPALMail.cs:220
IAllowEmailEnvelopeAndActions SaveTo(GPALFile directory)
Writes every attachment on every message Receive collected into the given directory,...
Definition GPALMail.cs:857
IAllowEmailServerSettingsAndName WithIMAPServerName(string serverName)
Sets the IMAP server hostname used for receiving mail, and forgets any POP server,...
Definition GPALMail.cs:102
string SMTPServerName
The SMTP server mail is sent through, as set by WithSMTPServerName or read from a settings file.
Definition GPALMail.cs:298
IGPALMail ToGPALObject()
Returns this instance cast to IGPALMail, allowing the fluent chain to be converted back to the root m...
Definition GPALMail.cs:92
string FromAddress
The address messages are sent from, as text, with the display name when there is one.
Definition GPALMail.cs:310
IAllowEmailEnvelopeAndActions WithName(string name)
Sets the display name on the address added just before this call, whether that was the from,...
Definition GPALMail.cs:526
IAllowEmailEnvelopeAndActions WithBody(string body)
Sets the message body, automatically detecting whether it is HTML or plain text via IsHtml(string) an...
Definition GPALMail.cs:500
IAllowEmailEnvelopeAndActions WithAttachment(GPALFile attachmentFile)
Adds the file(s) referenced by the given GPALFile as email attachments. Each filename in GPALFile....
Definition GPALMail.cs:464
IAllowEmailEnvelopeAndActions WithSubject(string subject)
Sets the subject line of the message.
Definition GPALMail.cs:514
string POPServerName
The POP server mail is read from, null when an IMAP server is the one named.
Definition GPALMail.cs:306
IAllowEmailServerSettingsAndName WithIMAPPortNum(int portNum=993)
Sets the port number used to connect to the IMAP server.
Definition GPALMail.cs:138
IAllowEmailEnvelopeAndActions Clear()
Resets the email envelope to a blank state: clears all recipient lists, subject, body text,...
Definition GPALMail.cs:875
IAllowEmailEnvelopeAndActions DeleteAll()
Deletes every message Receive() collected from the server, flagging and expunging them in one connect...
Definition GPALMail.cs:787
IAllowEmailServerSettingsAndName WithPassword(string password)
Sets the password used to authenticate with the mail server(s).
Definition GPALMail.cs:344
IAllowEmailEnvelopeAndActions Send()
Builds the message from the configured envelope (from/to/cc/bcc, subject, body, attachments) and send...
Definition GPALMail.cs:556
IAllowEmailServerSettingsAndName WithAcceptAnyCertificate(bool acceptAnyCertificate=true)
Send even when the server's certificate cannot be validated. Off unless a workflow says otherwise,...
Definition GPALMail.cs:171
IAllowEmailServerSettingsAndName WithPOPServerName(string serverName)
Sets the POP server hostname used for receiving mail, and forgets any IMAP server,...
Definition GPALMail.cs:115
IAllowEmailEnvelopeAndActions LoadSettings(GPALFile mailSettingsFile=null)
Loads this instance's GPALMailSettings from the given GPALFile via GPAL.Converter,...
Definition GPALMail.cs:895
IAllowEmailServerSettingsAndName WithPOPPortNum(int portNum=110)
Sets the port number used to connect to the POP server.
Definition GPALMail.cs:148
IAllowNameEnvelopeAndActions WithBccEmailAddress(string mailAddress)
Adds one or more "Bcc" recipients, parsed from a single string that may contain multiple addresses de...
Definition GPALMail.cs:402
IAllowEmailEnvelopeAndActions WithMaxMessages(int maxMessages)
Caps how many messages one Receive() fetches, taking the newest first. Every message by default,...
Definition GPALMail.cs:697
IAllowEmailServerSettingsAndName WithSMTPServerName(string serverName)
Sets the SMTP server hostname used for sending mail.
Definition GPALMail.cs:127
int IMAPPortNum
The port used to reach IMAPServerName.
Definition GPALMail.cs:304
static List< MailboxAddress > ParseEmailAddresses(string input)
Splits a single string containing one or more email addresses delimited by commas,...
Definition GPALMail.cs:928
IAllowNameEnvelopeAndActions WithToEmailAddress(string[] emailParts)
Adds one or more "To" recipients from an array of email address strings.
Definition GPALMail.cs:416
A simplified representation of a single email message retrieved by GPALMail.Receive.
Definition GPALMail.cs:984
ReceivedEmail Delete()
Deletes this message from the server, flagging it and expunging it in the one call....
Definition GPALMail.cs:1021
List< GPALFile > SaveTo(GPALFile directory)
Writes this message's attachments into the given directory, creating it when it is not there,...
Definition GPALMail.cs:1038
ReceivedEmail MarkAsRead()
Marks this message as read on the server, so the next Receive set to unread only leaves it behind....
Definition GPALMail.cs:1010
string Subject
The message's subject line.
Definition GPALMail.cs:986
List< string > Attachments
The filename of each attachment on the message, so a workflow can tell what came with it....
Definition GPALMail.cs:1027
string From
The message's "From" address, as a display string.
Definition GPALMail.cs:988
string MessageId
The message id the sender stamped on it, which is how the same message is recognised across runs.
Definition GPALMail.cs:996
string Body
The plain-text body of the message, falling back to the html body when the sender wrote no text part.
Definition GPALMail.cs:990
string HtmlBody
The html body of the message, null when the sender wrote none.
Definition GPALMail.cs:992
DateTime Received
When the message was sent, in local time.
Definition GPALMail.cs:994