Press "Enter" to skip to content

Inviare o leggere posta elettronica usando Outlook da C#

Si tratta probabilmente di qualcosa che più di qualcuno considererà obsoleta, ma a mio avviso se vogliamo inviare dei messaggi di posta o leggerli da un nostro programma nel modo più corretto è ancora uno dei metodi più opportuni.

Perché usare l’interoperabilità con Outlook è opportuno

Si tratta di un’opinione personale, non di un affermazione assoluta, il framework .net ha tutte le classi per la gestione dei protocolli SMTP, IMAP e POP3 ed esistono librerie professionali per creare dei veri e propri client di posta nelle nostre applicazioni. Quindi perché per Sabrina è opportuno usare l’Interoperabilità con Outlook, delle librerie antiche e non native ma create come wrapper tanto tempo fa.

Per un motivo ben preciso, ovvero per non prenderci la responsabilità del trattamento della posta. soprattutto quella spedita, infatti, se noi spediamo delle e-mail direttamente utilizzando il client SMTP di .net dobbiamo sostituirci al client standard di posta e occuparci anche di sapere se le mail arrivano a destinazione e di tutto quello che qualsiasi client fa in questi casi, come ad esempio crearne un archivio e gestirlo.

E’ ovvio che ci sono applicazioni per cui questo non è indispensabile, come ad esempio una applicazione per inviare newsletter. Ma ci sono altre applicazioni in cui è indispensabile che le mail inviate dalla nostra applicazione siano gestite in modo corretto sotto ogni punto di vista. Ad esempio per questioni di sicurezza, di privacy, o per documentare legalmente l’invio dei messaggi.

A questo punto, l’uso delle vecchie Interop di Outlook ci permettono di scaricare ogni responsabilità su Outlook e sul Server di posta con cui Outlook lavora, sia questo Exchange o un qualsiasi altro server SMTP, IMAP o POP3.

Outlook infatti gestisce ogni aspetto della comunicazione via posta, le email verranno spedite, verranno gestite priorità, ricevuta di ritorno, ricevuta di lettura, archiviazione della mail sulla nostra cartella posta inviata e quant’altro. Inoltre Outlook gestisce gli attachments, il testo HTML, persino le immagini embedded se lo vogliamo, per cui perché creare qualcosa di complesso quando possiamo usare il nostro caro vecchio outlook.

Ovviamente questo fino a che continuerà ad esistere e funzionare, perché ho il sospetto che se venisse ritirato dal servizio, spedire email da un programma esterno diventerà molto, molto complicato.

Terminata la giustificazione per il codice che vi introdurrò, passiamo finalmente a quello che vi interessa di più.

Creare il progetto e referenziare la libreria Interoperability di Outlook

Prima di iniziare vi ricordo che per poter usare e testare questo codice dovete avere installato Outlook sul vostro PC, io uso Windows 10 e ho usato la versione 15.0.0.0 di Microsoft.Office.Interop.Outlook, ma qualsiasi versione precedente funziona ugualmente, perché il codice che vi mostrerò è stato scritto nel 2006 quindi con un office molto più antico.

Uso Visual Studio 2019 Preview ma potete ovviamente usare anche Visual Studio 2017 Per prima cosa selezioniamo Create new project.

Selezioniamo una applicazione WPF con il .net framework full non ho provato ma dubito che sia possibile usare le Interop con .net Core. Premiamo Next per passare alla schermata di definizione del progetto.

Ho chiamato la mia applicazione Interoperability e l’ho memorizzata su una cartella con lo stesso nome sulla mia cartella dei progetti Dotnetwork.

Una volta creato il progetto vediamo come referenziare la libreria di Outlook, premiamo tasto destro sul nodo References del progetto Interoperability e selezioniamo Add Reference…

Nella finestra Reference Manager, notate che ho aperto il nodo Assemblies il primo in alto e poi ho digitato Outlook nella casella di ricerca in alto a Destra, questo mi mostra le librerie Outlook che sono installate sulla mia macchina seleziono la libreria Microsoft.Office.Interop.Outlook 15.0.0.0 come vedete non è l’unica presente. Adesso siamo pronti per il codice.

La MainWindow.xaml per i test

La window di test sarà particolarmente limitata, infatti conterrà una TextBox ove visualizzare i risultati dei test e tre bottoni che lanceranno i test stessi. Non creerò una vera interfaccia utente.

<Window
	x:Class="Interoperability.MainWindow"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:local="clr-namespace:Interoperability"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	Title="Interoperabilità con Outlook"
	Width="800"
	Height="450"
	mc:Ignorable="d">
	<Grid>
 
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="*" />
			<RowDefinition Height="Auto" />
		</Grid.RowDefinitions>
		<TextBox
			Grid.Row="1"
			Margin="4,2,4,2"
			HorizontalAlignment="Stretch"
			VerticalAlignment="Stretch"
			HorizontalContentAlignment="Left"
			VerticalContentAlignment="Top"
			AcceptsReturn="True"
			AcceptsTab="True"
			Text="{Binding ResultText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
			TextWrapping="Wrap"
			VerticalScrollBarVisibility="Auto" />
		<StackPanel
			Grid.Row="2"
			FlowDirection="RightToLeft"
			Orientation="Horizontal">
			<Button
				Margin="4,2,4,2"
				Padding="10,4,10,4"
				Click="Test_Interop_Outlook">
				<TextBlock Margin="4,2,4,2" Text="Lista oggetti Outlook" />
			</Button>
 
			<Button
				Margin="4,2,4,2"
				Padding="10,4,10,4"
				Click="Test_Send_Outlook">
				<TextBlock Margin="4,2,4,2" Text="Spedizione Mail Outlook" />
 
			</Button>
 
			<Button
				Margin="4,2,4,2"
				Padding="10,4,10,4"
				Click="Test_Read_Outlook">
				<TextBlock Margin="4,2,4,2" Text="Lettura Mail Outlook" />
 
			</Button>
 
		</StackPanel>
 
	</Grid>
</Window>

Ecco la window in tutta la sua semplicità, come vedete ci sono solo una Textbox e 3 button che eseguono le 3 operazioni fondamentali che servono per lavorare con outlook, poi starà a voi che leggerete implementare qualcosa di più sofisticato, se lo riterrete utile.

Vediamo ora cosa appare dietro ai tre button, sorprendentemente, la cosa più complicata è la lettura della lista oggetti Outlook, il primo dei 3 button. Perché tale lista è complicata? Perché è una Tree e come tale implica la creazione di una funzione ricorsiva. La lettura della tree però è quel che vi servirà per poter agganciare ed utilizzare gli oggetti di Outlook o meglio gli account di posta, i calendari e i contatti che sono presenti sulla vostra installazione di outlook.

Cito i Calendari ed i Contatti perché li vedrete nella lista della vostra tree, ma non sono oggetto di questo articolo.

private List<FoldersExplorerItemmFolders;
 
public List<FoldersExplorerItemFolders
{
	get
	{
		return mFolders;
	}
	private set
	{
		mFolders = value;
	}
}
 
public const string FLD_ResultText = "ResultText";
 
private string mResultText;
 
public string ResultText
{
	get
	{
		return mResultText;
	}
	set
	{
		mResultText = value;
		OnPropertyChanged(FLD_ResultText);
	}
}

Prima di tutto due Property, la prima è la lista che ho chiamato Folders anche se in realtà è una lista degli oggetti della tree di Outlook, ma se guardiamo la tree di Outlook come una replica della tree del file system ci sta.

La seconda è la property mappata sulla Textbox dove andremo a scrivere i risultati dei nostri test.

public MainWindow()
{
	InitializeComponent();
	DataContext = this;
	Folders = new List<FoldersExplorerItem>();
}

Il costruttore della Window, dove agganciamo il DataContext alla window stessa in modo che il Binding funzioni correttamente, ed istanziamo la collection degli oggetti Outlook. Come vedete è una collection di oggetti di tipo FoldersExplorerItem, una classe che ho creato al volo per memorizzare le cose necessarie a lavorare sulle email.

Il metodo che legge la lista degli oggetti presenti sulla tree dell’object explorer di outlook, quelli che sono chiamati Folder

using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.ComponentModel;
 
namespace Interoperability.Entities
{
	public class FoldersExplorerItem 
	{
 
		private string mIDEntry;
 
		public string IDEntry
		{
			get
			{
				return mIDEntry;
			}
			set
			{
				mIDEntry = value;
			}
		}
 
 		private string mIDParent;
 
		public string IDParent
		{
			get
			{
				return mIDParent;
			}
			set
			{
				mIDParent = value;
			}
		}
 
		private string mName;

		public string Name
		{
			get
			{
				return mName;
			}
			set
			{
				mName = value;
			}
		}
 
		private string mDescription;
 
		public string Description
		{
			get
			{
				return mDescription;
			}
			set
			{
				mDescription = value;
			}
		}
 
		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			sb.AppendFormat("{0} - {1} : {2}"IDParent == null ? "*root*" : IDParentIDEntryDescription);
			sb.AppendLine();
			return (sb.ToString());
		}
 
	}
}

Potete notare come l’oggetto FolderExplorerItem contenga solo le property per poter memorizzare i seguenti dati:

  • IDEntry – un unique identifier che identifica l’oggetto
  • IDParent – l’IDEntry dell’oggetto parent (l’account di posta di una cartella ad esempio)
  • Name – Il nome dell’oggetto
  • Description – Un campo descrittivo dove metteremo il nome e il tipo di oggetto

Oltre a questo, per permetterci di visualizzare i dati letti sulla nostra TextBox, ho implementato una funzione che crea una stringa con il contenuto dell’oggetto.

Ed ora, iniziamo a vedere il primo event Handler, la lettura degli oggetti di Outlook.

private async void Test_Interop_Outlook(object senderRoutedEventArgs e)
{
	try
	{
		Cursor c = this.Cursor;
		try
		{
			this.Cursor = Cursors.Wait;
			Task cTask = Task.Run(() => GetAllFolders());
			await cTask;
			StringBuilder sb = new StringBuilder();
			foreach (FoldersExplorerItem item in Folders)
			{
				sb.AppendLine(item.ToString());
			}
			ResultText = sb.ToString();
 
		}
		catch (Exception iex)
		{
			MessageBox.Show(iex.Message"ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
		}
		finally
		{
			this.Cursor = c;
		}
 
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message"ERRORE"MessageBoxButton.OK,  MessageBoxImage.Error);
	}
}

Anche se sembra un po’ complicato, usando 2 Try nidificati, in realtà quello interno serve solo per gestire la visualizzazione del cursore di attesa. mentre il succo di questo metodo è la chiamata al metodo GetAllFolders, che va a leggere tutta la Tree degli oggetti. c’è una particolarità in questo metodo, ovvero la parola chiave async e la presenza di un oggetto Task e di un altra parola chiave, await. A cosa servono e cosa rappresentano? La lettura della Tree di Outlook può essere una operazione piuttosto lunga, se vi sono vari account e se avete l’abitudine di archiviare i messaggi in molte sottocartelle, pertanto l’ho inserita in un metodo asincrono, generato usando l’oggetto Task del framework. In questo modo l’interfaccia utente non si blocca mentre viene caricato il dato e il cursore di attesa appare subito. All’uscita dalla lettura degli oggetti, creo uno StringBuilder in cui scrivo il contenuto di tutti gli oggetti letti.

Il risultato della lettura sarà una lista più o meno lunga (in base agli account che gestite sul vostro Outlook) contenente le caselle di posta (che non hanno ID parent e sono riconoscibili per il *root*) e tutte le cartelle e sottocartelle ad esse collegate.

Dopo le caselle di posta compariranno come oggetto root sia i Contacts che i calendari.

Ma vediamo ora il metodo GetAllFolders che va fisicamente a leggere i dati in Outlook.

private void GetAllFolders()
{
	Folders.Clear();
	Outlook.Application oApp = new Outlook.Application();
	Outlook.NameSpace session = oApp.Session;
	List<stringnames = new List<string>();
	GetFolders(session.Foldersnull);
}

Come vedete è molto semplice, perché istanzia l’applicazione Outlook, ne acquisisce la Session (che non è altro che l’oggetto che contiene la tree degli oggetti), che guardacaso è una property che si chiama Folders. A questo punto, non fa altro che passare la tree degli oggetti ad un altro metodo che si chiama GetFolders. Questo è il metodo più complesso da noi creato, perché è un metodo ricorsivo, che percorre la tree con tutti i suoi rami leggendo tutti gli oggetti in essa contenuti e aggiungendoli alla nostra collection Folders. Vediamo come è fatta.

private void GetFolders(Outlook.Folders pFoldersFoldersExplorerItem parent)
{
	foreach (Outlook.Folder item in pFolders)
	{
		StringBuilder sb = new StringBuilder();
 
		FoldersExplorerItem info = new FoldersExplorerItem();
		if (parent != null)
		{
			info.IDParent = parent.IDEntry;
			
			
		} 
		info.IDEntry = item.EntryID;
		info.Name = item.Name;
		sb.AppendFormat("Folder: {0} Item Type: {1} "item.Nameitem.DefaultItemType.ToString());
		info.Description = sb.ToString();
		
 
		Folders.Add(info);
		if (item.Folders != null && item.Folders.Count > 0)
		{
			
			GetFolders(item.Foldersinfo);
			
		}
	}
}

Che cosa fa questo metodo, riceve in input la lista degli oggetti folder del ramo Parent (la prima volta si tratta della Root degli oggetti) e per ogni oggetto della lista genera un oggetto FolderExplorerItem che aggiunge alla collection. Fatto questo controlla se l’oggetto a sua volta contiene degli altri “Folders” nella sua Property chiamata Folders. Se questo è vero (ad esempio nel caso di un account di posta se contiene le cartelle (posta in arrivo, posta inviata, posta cancellata ecc.) ) Se questo è vero, il metodo chiama se stesso passando, nel primo parametro la collezione delle cartelle, nel secondo il nostro oggetto FolderExplorerItem da cui andrà a prelevare l’IDEntry che sarà posto nell’IDParent così che conosciamo il parent di ogni elemento della nostra lista piana.

Con questo metodo, siamo in grado di produrre la lista di tutti gli oggetti dell’object explorer di Outlook.

Il metodo che spedisce una e-mail da una delle caselle dei nostri account

Come vedrete è piuttosto semplice spedire una email con Outlook, sia essa di testo, in HTML e contenga o meno dei file allegati.

private void Test_Send_Outlook(object senderRoutedEventArgs e)
{
	try
	{
		SendMail("Test Invio Mail da WPF""destinatario@suodominio.net""Testo della mail inviata da me.",
			"mittente@miodominio.it"Outlook.OlBodyFormat.olFormatPlain );
		ResultText = "La mail Test è stata inviata.";
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message"ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
}

Il metodo legato all’event handler è semplice, infatti chiama un altro metodo che compone la mail, in questo caso in formato testo, ma basta inserire dell’HTML all’interno della variabile “Testo della mail inviata da me.” e cambiare il formato da olFormatPlain ad olFormatHtml per creare una mail in HTML, non solo, si può anche utilizzare il formato RTF volendo. Se volete inserire delle immagini nel messaggio, vi consiglio di inserirle come stringhe in base 64 se sono icone è il modo più semplice. in tutti gli altri casi sarebbe meglio fossero sempre allegate non inserite nel testo.

Ma andiamo a vedere il metodo di trasmissione vera e propria.

public void SendMail(string subjectstring pTostring BodyTextstring senderAccountName,
	Outlook.OlBodyFormat format = Outlook.OlBodyFormat.olFormatPlain,
	Outlook.OlImportance importance = Outlook.OlImportance.olImportanceNormal,
	List<stringattachments = null)
{
	Outlook.Application oApp = new Outlook.Application();
	Outlook.NameSpace session = oApp.Session;
 
	Outlook.MailItem item = oApp.CreateItem(Outlook.OlItemType.olMailItem);
	item.Subject = subject;
	item.To = pTo;
	item.Body = BodyText;
	item.BodyFormat = format;
	item.SendUsingAccount = session.Accounts[senderAccountName];
	item.Importance = importance;
	item.ReadReceiptRequested = true;
	item.OriginatorDeliveryReportRequested = true;
 
	if (attachments != null)
	{
		foreach (string fileName in attachments)
		{
			Outlook.Attachment oatt = item.Attachments.Add(fileName);
		}
	}
	item.Send();
}

Per spedire una email, istanziamo un oggetto di tipo Outlook.MailItem, utilizzando l’opportuno metodo dell’interop ed inizializziamo le sue proprietà con i dati del messaggio, pertanto Oggetto, destinatario, Testo, formato, account da utilizzare per la trasmissione, importanza, e se vogliamo ricevuta di lettura e ricevuta di consegna, gli attachments vanno semplicemente aggiunti indicando il nome file e sarà poi Outlook a provvedere a prelevarli. Per spedire basta usare il Send dell’oggetto MailItem stesso. Come vedete è tutto molto semplice e diretto. ora dovete solo provare usando le vostre caselle di posta.

Leggere il contenuto di una cartella in uno degli account di posta elettronica

Vediamo ora il terzo ed ultimo dei metodi per lavorare con Outlook, ovvero la lettura dei messaggi di posta di una cartella di uno degli account che abbiamo mappato. Funziona sia con le caselle di posta normali sia con le caselle Pec, sia con le caselle POP3 che con le caselle IMAP perché pensa a tutto Outlook. E’ chiaro che la lettura legge i messaggi che sono stati scaricati sul vostro PC pertanto leggerà i messaggi memorizzati fino all’ultimo invia e ricevi eseguito, perché legge gli archivi locali, non fa interrogazioni al server.

private async void Test_Read_Outlook(object senderRoutedEventArgs e)
{
	try
	{
		Cursor c = this.Cursor;
		try
		{
			this.Cursor = Cursors.Wait;
 
			Task cTask = Task.Run(() => GetAllFolders());
			await cTask;
 
			FoldersExplorerItem casella = Folders.FirstOrDefault(x => x.Name == "info@miodominio.it");
			List<FoldersExplorerItemcartelle = Folders.Where(x => x.IDParent == casella.IDEntry).ToList();
			FoldersExplorerItem cartella = null;
			if (cartelle.Count > 0)
			{
				cartella = cartelle.FirstOrDefault(x => x.Name == "Posta in arrivo" );
			}
			if (cartella != null)
			{
				Outlook.Application oApp = new Outlook.Application();
				Outlook.NameSpace session = oApp.Session;
 
				Outlook.Folder folder = (Outlook.Folder)session.GetFolderFromID(cartella.IDEntryMissing.Value);
				if (folder != null)
				{
					StringBuilder mails = new StringBuilder();
 
					int count = 0;
					for (int j = folder.Items.Count-1j>=0j--)
					{
						object obj = folder.Items[j];
						count++;
						if (count > 10break;
						Outlook.MailItem mailItem = obj as Outlook.MailItem;
						if (mailItem != null)
						{
							mails.AppendLine(new string('-'80));
							mails.AppendFormat("Data spedizione: {0} {1} - Spedito da: {2}"mailItem.SentOn.ToShortDateString(), mailItem.SentOn.ToLongTimeString(), mailItem.SenderEmailAddress);
							if (mailItem.HTMLBody != null && mailItem.HTMLBody.Trim().Length > 0)
							{
								mails.AppendLine("Il messaggio è in HTML ma stampo solo il testo");
							}
							mails.AppendLine("+++");
							mails.AppendLine(mailItem.Body);
							mails.AppendLine("+++");
 
							if (mailItem.Attachments != null && mailItem.Attachments.Count > 0)
							{
								mails.AppendLine("Il messaggio contiene degli attachments");
								foreach (Outlook.Attachment att in mailItem.Attachments)
								{
									mails.AppendFormat("Nome: {0} File: {1} Dimensione ({2})"att.DisplayNameatt.FileNameatt.Size);
									mails.AppendLine();
									string outfolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "TestAttachments");
									if (!Directory.Exists(outfolder))
									{
										Directory.CreateDirectory(outfolder);
									}
									string attpath = Path.Combine(outfolderatt.FileName);
									att.SaveAsFile(attpath);
 
								}
							}
 
							mails.AppendLine(new string('^'80));
 
						}
					}
					ResultText = mails.ToString();
				}
			}
		}
		catch (Exception iex)
		{
			MessageBox.Show(iex.Message"ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
		}
		finally
		{
			this.Cursor = c;
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message"ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
}

In questo caso, non ho estratto il metodo di lettura dall’event handler, quindi è un po’ più lungo, ma anche se sembra complicato in realtà non lo è. anche in questo caso c’è un doppio try catch per gestire il cursore di attesa, poi le operazioni che facciamo sono molto semplici:

  • Per sicurezza, rileggiamo la tree degli oggetti di Outlook perché andremo a prendere gli oggetti Account e Cartella usando i dati letti dalla nostra funzione.
  • Usiamo una query Linq (FirstOrDefault) per cercare una delle nostre caselle di posta, in questo caso info@miodominio.it, voi sostituite la vostra casella di posta.
  • Con una seconda query, andiamo a prendere tutte le sottocartelle della nostra casella di posta (Where).
  • Se troviamo le cartelle, cerchiamo se esiste “Posta in arrivo” e nel caso la troviamo eseguiamo le seguenti operazioni.
  • Istanziamo l’applicazione Outlook
  • Acquisiamo la Session corrente
  • Recuperiamo la cartella tramite l’ID che abbiamo letto dall’oggetto recuperato dai nostri Folders.
  • Se l’oggetto è recuperato correttamente,  facciamo un ciclo sulla sua collection Items ma lo facciamo al contrario, partendo dall’ultimo e risalendo verso il primo, questo perché le mail sono memorizzate a partire dalla più vecchia andando verso la più recente quindi leggendole al contrario avremo in cima le ultime ricevute. Ho messo un limite alle prime 10 trovate, modificatelo o toglietelo per leggere quanto volete.
  • L’oggetto che ci viene reso dalla collection lo convertiamo in un Outlook.MailItem, se esso è veramente tale (potrebbe non esserlo), Iniziamo ad elaborarlo.
  • Al momento creo una stringa con il contenuto che inizia con una riga di trattini e termina con una riga di accenti circumflessi, stampo la data e ora di spedizione ed il mittente, poi verifico se fosse o meno in HTML e vado a recuperare il Body che contiene il testo semplice, mentre HTMLBody contiene l’HTML.
  • Se vi sono attachments, stampo la lista dei dati che li compongono. Nome descrittivo, nome del file e dimensione. Ho notato che in alcuni casi, la dimensione non viene riportata, è a zero, ma il file viene correttamente salvato. 
  • Per salvare l’attachment, creo una cartella “TestAttachments” sulla cartella Documenti dell’utente corrente e uso il metodo SaveAsFile dell’attachment per salvare il file.

E la lettura è fatta. Fate attenzione, che soprattutto per le mail lette dalla Pec, l’attachment con i dati XML relativi al messaggio, alle firme e allo status, hanno tutti il medesimo nome quindi bisogna avere un attimo di attenzione per generare correttamente gli attachment di ogni mail se vogliamo conservare i dati.

Per questo articolo mi fermo qui, se avete domande siete invitati ad usare il modulo di contatto in alto a destra con la bustina.