Integrations

Sending and Reading Email with GPAL.Mail

Server Settings

GPAL.Mail starts with server settings: a server name and optional port for SMTP going out, and IMAP or POP3 coming in. You configure whichever direction the workflow needs and add credentials once. Credentials do not have to be in the source. WithCredentials takes an ICredentials built once from a vault, so a workflow can be checked in without its password, and the same credential works for a browser or a REST call. The settings can also live in a file: SaveSettings writes them to GPALMail.yaml beside the exe, and a new GPAL.Mail loads that file automatically. Anything the code sets explicitly runs after the load and therefore wins. Only one incoming server is kept, so naming an IMAP server forgets a POP server and the other way round.

// the mail object first, then the server settings and the envelope on it

IGPALMail mail = GPAL.Mail.ToGPALObject();


// Built once from a vault, and the same one works for a browser or a REST call

ICredentials mailLogin = GPAL.CredentialsFor(CredentialServiceType.Bitwarden)

.GetCredentialsFor("mail.example.com")

.ToGPALObject();


IGPALMail mail = mail

.WithSMTPServerName("smtp.example.com")

.WithSMTPPortNum(587)

.WithIMAPServerName("imap.example.com")

.WithCredentials(mailLogin) // or WithUserName and WithPassword

.WithFromEmailAddress("automation@example.com")

.WithName("Nightly Automation")

.ToGPALObject();


// Keep them for next time, beside the exe

mail.SaveSettings();


// What it ended up using, whether that came from code or from the file

GPAL.PublishSimpleEvent(GPALEventType.NOTICE, $"[{mail.SMTPServerName}]:[{mail.SMTPPortNum}] in via [{mail.IMAPServerName}]");

Composing and Sending

Composing is a short fluent chain: from, to, cc, and bcc addresses, a subject, a body, and any attachments. WithName follows an address to give it a display name, so the same call names the sender or any recipient. A path works for an attachment, which is how a run mails its own log: the logger reports where it actually wrote through LogFullPath, and that goes straight to WithAttachment. Send throws when it cannot deliver, so there is no quiet failure to check for.

mail.WithToEmailAddress("team@example.com").WithName("Ops Team")

.WithSubject("Nightly report ready")

.WithBody("The nightly export finished. See attached.")

.WithAttachment((GPALFile)"report.csv")

.WithAttachment(runLogger.LogFullPath) // where the logger actually wrote

.Send();

Reading the Inbox, and Finishing With It

Receive reads from the configured incoming server. WithUnreadOnly and WithMaxMessages decide how much it is allowed to bring back, which matters because the default is the whole mailbox. The overload with an out parameter hands the messages over in the same call, each one carrying its subject, from address, body, when it was sent, and the filename of every attachment. Attachments are more than names, so SaveTo writes them to disk: on the mail object it saves the attachments of every message received, and on a single ReceivedEmail it saves just that one and hands back a GPALFile for each file written, ready for the next step of the workflow. Then the message has to be taken out of the way, or the next run does all of it again: MarkAsRead and Delete do that for one message, MarkAllAsRead and DeleteAll for the batch. That is the whole shape of an inbox-watching automation: fetch what is new, look at what came, save what matters, mark it handled, and carry on with the files. Clear resets the envelope between messages while keeping the server settings.

// Only the new mail, and only as much of it as this run will finish

mail.WithUnreadOnly(true)

.WithMaxMessages(25)

.Receive(out List<ReceivedEmail> received);


foreach (ReceivedEmail email in received)

{

if (false == email.Subject.Contains("Invoice")) continue;


List<GPALFile> saved = email.SaveTo((GPALFile)@"C:Inboxinvoices");


foreach (GPALFile invoice in saved)

GPAL.PublishSimpleEvent(GPALEventType.NOTICE, $"Saved [{invoice.Filename}] from [{email.From}]");


email.MarkAsRead(); // a run that dies before here finds it unread next time

}

💬 Ask GPAL