Envoyer des mails avec Delphi

Robin Valtot

Ce tutoriel vise à montrer qu'il existe plusieurs façons d'envoyer un mail avec Delphi. Un serveur SMTP, le pilotage d'Outlook et l'ensemble MAPI seront tour à tour utilisés à cette fin.

- Titre : Envoyer des mails avec Delphi

- Auteur : Robin Valtot

- Parution : 27 février 2017

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Robin Valtot. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Les courriers électroniques

- II. Création du projet

- III. Stocker les données

- IV. Paramétrage

- V. Les différents types de messages

- VI. Serveur SMTP

- VI-A. Envoyer un message texte

- VI-B. Envoyer un texte HTML

- VI-C. Ajouter des pièces jointes

- VII. Outlook

- VII-A. Mise en place

- VII-B. Ajouter des pièces jointes

- VIII. MAPI

- VIII-A. Utiliser MAPI

- VIII-B. Accusé de réception

- VIII-C. Ajouter une pièce jointe

- IX. Conclusion

I - Les courriers électroniques

Le courrier électronique, courriel, e-mail ou simplement mail est un service de transmission de messages écrits et de documents envoyés électroniquement via un réseau informatique (local ou par Internet), directement dans la boîte aux lettres électronique d'un destinataire.

Il est possible d'envoyer des mails avec Delphi en utilisant différentes méthodes :

- utiliser un serveur SMTP pour transférer le courrier vers le serveur de messagerie ;

- piloter Outlook pour écrire et envoyer le message ;

- utiliser un ensemble standardisé de fonctions, MAPI.

Les sources sont disponibles sur github :

- https://github.com/retwas/VCL-Email-with-SMTP-Mapi-Outlook

II - Création du projet

Nous allons créer un nouveau projet Delphi avec une fenêtre appelée uFormEmail. Celle-ci va nous permettre de saisir les données de notre message. Vous pouvez partir directement des sources à télécharger.

III - Stocker les données

Pour gérer les données d'un mail, nous allons utiliser un enregistrement (record). Celui-ci permet de stocker les données communes des messages.

type
TDataMessage = record
SendTo:      string;
SendCC:      string;
SendBCC:     string;
Sender:      string;
MailObject:  string;
MailMessage: string;
end;

IV - Paramétrage

Du paramétrage sera déclaré dans la partie implementation de l'unité pour être visible uniquement à cet endroit. Pour gérer les envois, nous allons avoir besoin de différentes unités référencées dans des uses, de constantes qui correspondront à nos boutons radios, ainsi que différents types d'exceptions.

implementation

uses
// SMTP
IdSMTP, IdExplicitTLSClientServerBase, IdMessage, IdSSLOpenSSL,
// Outlook
System.Win.ComObj,
// MAPI
Winapi.Mapi,
Math;

type
TMessageSMTPError    = class(Exception);
TMessageOutlookError = class(Exception);
TMessageMAPIError    = class(Exception);

const
// correspondance avec le RadioGroup
CST_SMTP    = 0;
CST_OUTLOOK = 1;
CST_MAPI    = 2;

V - Les différents types de messages

Un message doit contenir une méthode pour être envoyé et une pour stocker les données de l'enregistrement. Pour cela, nous allons utiliser une interface.

IMessage = interface
['{E76ABC78-18BF-48C5-9D48-21CDFC56A4C1}']
/// <summary>Permet d'envoyer un message en SMTP, en MAPI, ou avec Outlook</summary>
procedure Send;
/// <summary>Permet de renseigner un TDataMessage contenant les données de l'email à envoyer</summary>
procedure SetDataMessage(aDataMessage: TDataMessage);
end;

Le raccourci clavier Ctrl+Maj+G permet de générer automatiquement un GUID.

L'avantage d'utiliser cette méthode est de pouvoir appeler la méthode Send, le type du message n'important pas. Nos prochaines classes vont donc hériter de IMessage et de TInterfacedObject. Il est recommandé d'utiliser ce dernier, car c'est un descendant de IInterface : cela signifie qu'il gère automatiquement les références et la gestion mémoire des objets.

Pour rappel, les méthodes d'une interface doivent obligatoirement être déclarées dans les classes descendantes.

TMessageMapi = class(TInterfacedObject, IMessage)
strict private
FDataMessage: TDataMessage;
public
procedure Send;
procedure SetDataMessage(aDataMessage: TDataMessage);
end;

TMessageOutlook = class(TInterfacedObject, IMessage)
strict private
FDataMessage: TDataMessage;
public
procedure Send;
procedure SetDataMessage(aDataMessage: TDataMessage);
end;

Pour le SMTP, nous avons besoin de paramètres supplémentaires. Il s'agit d'un identifiant, d'un mot de passe et des informations du serveur. Pour notre exemple qui ne sert que d'illustration, ces derniers seront renseignés « en dur » dans l'application.

TMessageSMTP = class(TInterfacedObject, IMessage)
strict private
FDataMessage: TDataMessage;
FLogin:       string;
FPassword:    string;
FServer:      string;
FPort:        integer;
public
constructor Create(aLogin, aPassword: string);

procedure Send;
procedure SetDataMessage(aDataMessage: TDataMessage);
end;

constructor TMessageSMTP.Create(aLogin, aPassword : string);
begin
inherited Create;

FLogin    := aLogin;
FPassword := aPassword;
FServer   := 'smtp.office365.com';
FPort     := 587;
end;

Le bouton « Envoyer » va permettre de gérer le curseur, créer le type de message en fonction du mode d'envoi et affecter les valeurs dans un TDataMessage.

procedure TFormEmail.btnEnvoyerClick(Sender: TObject);
var
SendMessage: IMessage;
DataMessage: TDataMessage;
Cursor     : TCursor;
begin
Cursor := Screen.Cursor;

try
Screen.Cursor := crHourGlass;

try
case RadioGroup.ItemIndex of
CST_SMTP    : SendMessage := TMessageSMTP.Create(edLogin.Text, edPassword.Text);
CST_OUTLOOK : SendMessage := TMessageOutlook.Create;
else
SendMessage := TMessageMapi.Create;
end;

DataMessage.SendTo      := edTo.Text;
DataMessage.SendCC      := edCC.Text;
DataMessage.SendBCC     := edBCC.Text;
DataMessage.Sender      := edSender.Text;
DataMessage.MailObject  := edObject.Text;
DataMessage.MailMessage := Memo.Text;

SendMessage.SetDataMessage(DataMessage);
SendMessage.Send;
except
on E : Exception do
MessageDlg('Erreur lors de l''envoi de l''email.' + #10#13 + e.Message, mtError, [mbOK], 0);
end;
finally
Screen.Cursor := Cursor;
end;
end;

La variable SendMessage est déclarée du type de l'interface et peut être de la forme de l'une des trois méthodes d'envoi. Le except permet de gérer les exceptions qui peuvent être levées durant le processus de création et d'envoi du message.

En fonction de l'option choisie par l'utilisateur, l'envoi sera aiguillé vers la bonne méthode.

La procédure SetDataMessage sera identique pour les différents types.

procedure TMessageSMTP.SetDataMessage(aDataMessage: TDataMessage);
begin
FDataMessage := aDataMessage;
end;

VI - Serveur SMTP

L'une des méthodes les plus rapides est d'utiliser l'envoi des courriers en SMTP (Simple Mail Transfer Protocol) grâce aux composants Indy.

Trois composants sont nécessaires :

- TIdSMTP : pour se connecter au serveur ;

- TIdSSLIOHandlerSocketOpenSSL : pour sécuriser les connexions réseau ;

- TIdMessage : il s'agit de notre message.

Pour utiliser TLS/SSL avec Indy, il faut télécharger les fichiers bibliothèques libeay32.dll et ssleay32.dll disponibles à l'adresse suivante et les copier à côté de l'exécutable :

- http://indy.fulgan.com/SSL/

VI-A - Envoyer un message texte

Dans la méthode Send du type TMessageSMTP, il faut créer nos composants Indy.

procedure TMessageSMTP.Send;
var
IdSMTP    : TIdSMTP;
IdSSLIO   : TIdSSLIOHandlerSocketOpenSSL;
IdMessage : TIdMessage;
begin
// création des composants Indy
IdSMTP    := TIdSMTP.Create(nil);
IdMessage := TIdMessage.Create(nil);
IdSSLIO   := TIdSSLIOHandlerSocketOpenSSL.Create(nil);

Nous allons à présent spécifier les valeurs de notre serveur SMTP comme l'adresse, le port (25 par défaut), ainsi que les informations de l'utilisateur si elles sont saisies.

try
// adresse et port du serveur SMTP
IdSMTP.AuthType := satDefault;
IdSMTP.Host     := FServer;
IdSMTP.Port     := FPort;

// utilisation du mode sécurisé (ou pas si l'envoi en anonyme est autorisé)
if (FLogin <> '') and (FPassword <> '') then
begin
IdSSLIO.SSLOptions.Method := sslvTLSv1;
IdSMTP.Username  := FLogin;
IdSMTP.Password  := FPassword;
IdSMTP.IOHandler := IdSSLIO;
IdSMTP.UseTLS    := utUseExplicitTLS;
end;

Nous pouvons maintenant nous connecter au serveur. Si celui-ci ne répond pas, nous allons lever une exception.

// connexion au serveur
try
IdSMTP.Connect;
except
on e : Exception do
raise TMessageSMTPError.Create('Erreur de connexion au serveur SMTP' + #10#13 + e.Message);
end;

Si la connexion a réussi, nous pouvons nous occuper du message.

IdMessage.Clear;

// paramétrage de l'expéditeur
IdMessage.From.Text           := FDataMessage.Sender;
IdMessage.ReplyTo.Add.Address := FDataMessage.Sender;

// ajout des destinataires
if FDataMessage.SendTo <> '' then
IdMessage.Recipients.Add.Address := FDataMessage.SendTo;

if FDataMessage.SendCC <> '' then
IdMessage.CCList.Add.Address     := FDataMessage.SendCC;

if FDataMessage.SendBCC <> '' then
IdMessage.BccList.Add.Address    := FDataMessage.SendBCC;

// objet du message
IdMessage.Subject := FDataMessage.MailObject;

// paramétrage de la date et de la priorité
IdMessage.Date     := Now;
IdMessage.Priority := mpNormal;

// il est possible de paramétrer un l'accusé de lecture
// idMessage.ReceiptRecipient.Address := adresse de l'expediteur;

// corps du message
IdMessage.Body.Text := FDataMessage.MailMessage;

Nous sommes connectés au serveur SMTP et le message est prêt : nous pouvons l'envoyer.

try
IdSMTP.Send(IdMessage);
except
on e : Exception do
raise TMessageSMTPError.Create('Erreur lors de l''envoi de l''email.' + #10#13 + e.Message);
end;

À la sortie, nous allons fermer la connexion au serveur et libérer les différents composants.

finally
IdSMTP.Disconnect;

FreeAndNil(IdSSLIO);
FreeAndNil(IdMessage);
FreeAndNil(IdSMTP);
end;
end;

VI-B - Envoyer un texte HTML

Il est possible d'envoyer un texte formaté en HTML. Pour cela, nous devons utiliser un TIdText (IdText). Au lieu de spécifier le texte dans la propriété Body.Text du message, voici comment faire :

IdText := TIdText.Create(IdMessage.MessageParts);
IdText.Body.Text       := FDataMessage.MailMessage; // texte en HTML
IdText.ContentType     := 'text/html';
IdText.ContentTransfer := 'quoted-printable';
IdText.CharSet         := 'utf-8';
IdMessage.ContentType  := 'multipart/mixed';

VI-C - Ajouter des pièces jointes

Pour ajouter des pièces jointes à un message, il faut utiliser la propriété MessageParts. Il suffit de créer un TIdAttachementFile, de lui spécifier le message auquel la pièce sera ajoutée, ainsi que le chemin du fichier.

TIdAttachmentFile.Create(IdMessage.MessageParts, 'D:\Fichier.pdf');

VII - Outlook

VII-A - Mise en place

Il est aussi possible d'envoyer des mails en récupérant ou en créant une instance d'Outlook. Nous allons avoir besoin des constantes utilisée par Outlook : elles seront déclarées localement dans la méthode Send. Pour récupérer Outlook, nous allons créer une fonction GetOutlook qui va nous renvoyer une instance de type OLEVariant.

procedure TMessageOutlook.Send;
const
// constantes utilisée par outlook
// https://msdn.microsoft.com/en-us/library/office/aa219371(v=office.11).aspx
CSTL_olMailItem        = 0;
CSTL_olByValue         = 1;
CSTL_olTo              = 1;
CSTL_olCC              = 2;
CSTL_olBCC             = 3;
CSTL_olEditorWord      = 4;

// constantes issues de Outlook2010.pas
CSTL_olFormatUnspecified = $00000000;
CSTL_olFormatPlain       = $00000001;
CSTL_olFormatHTML        = $00000002;
CSTL_olFormatRichText    = $00000003;

// fonction permettant de récupérer ou de créer une instance de Outlook
function GetOutlook(var bFind : boolean) : OLEVariant;
begin
bFind  := False;
Result := Unassigned;

try
// récupération de la référence vers Outlook
Result := GetActiveOleObject('Outlook.Application');
bFind  := True;
except
try
// création d'une nouvelle instance si
// l'application n'a pas été trouvée
Result := CreateOleObject('Outlook.Application');
bFind  := True;
except
bFind := False;
end;
end;
end;

Si notre fonction ne retourne aucune instance d'Outlook, nous allons arrêter l'envoi. Si c'est bon, nous pouvons continuer et créer le message (ovMailItem) grâce à une variable de type OLEVariant.

var
ovMailItem      : OLEVariant;
ovOutlook       : OleVariant;
bTrouve         : boolean;
sSignature      : string;
vDestinataire   : Variant;
begin
try
ovOutlook := GetOutlook(bTrouve);

if not bTrouve then
begin
// si outlook est fermé ou ouvert en tant que administrateur
raise TMessageOutlookError.Create('Application Outlook non trouvée.')
end
else
begin
// création d'un email
ovMailItem := ovOutlook.CreateItem(CSTL_olMailItem);

Il est possible d'avoir plusieurs comptes paramétrés sur sa messagerie, mais pour faire simple, nous allons utiliser le premier profil et rédiger notre mail en HTML.

// s'il y a plusieurs profils dans outlook, nous allons utiliser le premier
if ovOutlook.Session.Accounts.Count > 0 then
ovMailItem.sendUsingAccount := ovOutlook.Session.Accounts.Item(1);

// mise en place du OlBodyFormat (olFormatRichText, olFormatHTML, olFormatPlain)
// nous allons utiliser le texte html
ovMailItem.BodyFormat := CSTL_olFormatHTML;

Nous pouvons alors ajouter les destinataires et l'objet.

// ajout des destinataires
if FDataMessage.SendTo <> '' then
begin
vDestinataire      := ovMailItem.Recipients.Add(FDataMessage.SendTo);
vDestinataire.Type := CSTL_olTo;
end;

if FDataMessage.SendCC <> '' then
begin
vDestinataire      := ovMailItem.Recipients.Add(FDataMessage.SendCC);
vDestinataire.Type := CSTL_olCC;
end;

if FDataMessage.SendBCC <> '' then
begin
vDestinataire      := ovMailItem.Recipients.Add(FDataMessage.SendBCC);
vDestinataire.Type := CSTL_olBCC;
end;

// ajouter un accusé de lecture
// ovMailItem.ReadReceiptRequested := True;

ovMailItem.Subject := FDataMessage.MailObject;

Il existe une astuce pour récupérer la signature présente dans Outlook si l'utilisateur a choisi de l'ajouter automatiquement aux nouveau messages. Nous allons ouvrir le message, mais sans le rendre visible, puis nous pourrons récupérer le code HTML de la fenêtre.

// il est possible d'ouvrir le message sans l'afficher :
// ceci permet par exemple de récupérer la signature
// de l'utilisateur si elle est ajoutée automatiquement
ovMailItem.Display(False);

// récupération de la signature (s'il y en a une)
sSignature := ovMailItem.HTMLBody;

// concaténation du texte du message et de la signature
ovMailItem.HTMLBody := FDataMessage.MailMessage + sSignature;

Il ne reste plus qu'à envoyer le message.

// il est possible d'afficher le mail avant de l'envoyer avec ovMailItem.Display;
ovMailItem.Send;
end;
finally
ovMailItem := Unassigned;
end;
end;

Il est possible d'afficher le mail et de laisser l'utilisateur l'envoyer lui-même en utilisant la procédure Display.

VII-B - Ajouter des pièces jointes

Pour ajouter des documents sur un mail, nous allons utiliser notre constante CST_olByValue et la fonction Add qui prend en paramètres :

- Source : le chemin vers le fichier ;

- Type : 1 pour indiquer qu'il s'agit d'une copie du fichier d'origine (et non une référence) ;

- Position : 0 la pièce jointe est masquée, 1 la pièce doit être en début du corps du message, supérieure à 1 elle sera à la fin ;

- DisplayName : le nom de la pièce jointe

ovMailItem.Attachments.Add('D:\fichier.doc', CST_olByValue, 1, 'D:\fichier.doc');

VIII - MAPI

Pour utiliser un envoi MAPI, il faut avoir un client de messagerie sur le poste. L'utilisation est conditionnée à sa disponibilité et à son paramétrage.

VIII-A - Utiliser MAPI

Nous allons créer plusieurs variables. MapiDest sera initialisée à nil.

MapiMessage    : TMapiMessage;
MapiDest       : PMapiRecipDesc;
MapiExpediteur : TMapiRecipDesc;
MAPI_Session   : Cardinal;
MapiResult     : Cardinal;
MAPIError      : DWord;

MapiMessage et MapiExpediteur seront paramétrées avec les données du TDataMessage.

begin
MapiDest := nil;

try
// FillChar permet de remplir une suite d'octets avec une valeur, ici 0
FillChar(MapiMessage, Sizeof(TMapiMessage), 0);
MapiMessage.lpszSubject  := PAnsiChar(AnsiString(FDataMessage.MailObject));
MapiMessage.lpszNoteText := PAnsiChar(AnsiString(FDataMessage.MailMessage));

// même traitement pour paramétrer l'expéditeur
FillChar(MapiExpediteur, Sizeof(TMapiRecipDesc), 0);
MapiExpediteur.lpszName    := PAnsiChar(AnsiString(FDataMessage.Sender));
MapiExpediteur.lpszAddress := PAnsiChar(AnsiString(FDataMessage.Sender));
MapiMessage.lpOriginator   := @MapiExpediteur;

Pour les destinataires, il faut initialiser le nombre de destinataires avant l'affectation.

// paramétrage du nombre de destinataires
MapiMessage.nRecipCount := IfThen(FDataMessage.SendTo <> '', 1) + IfThen(FDataMessage.SendCC <> '', 1) + IfThen(FDataMessage.SendBCC <> '', 1);
// et allocation de la mémoire nécessaire
MapiDest                := AllocMem(SizeOf(TMapiRecipDesc) * MapiMessage.nRecipCount);
// paramétrage des destinataires sur notre message MAPI
MapiMessage.lpRecips    := MapiDest;

if FDataMessage.SendTo <> '' then
begin
MapiDest.lpszName     := PAnsiChar(AnsiString(FDataMessage.SendTo));
MapiDest.ulRecipClass := MAPI_TO;
end;

if FDataMessage.SendCC <> '' then
begin
MapiDest.lpszName     := PAnsiChar(AnsiString(FDataMessage.SendCC));
MapiDest.ulRecipClass := MAPI_CC;
end;

if FDataMessage.SendBCC <> '' then
begin
MapiDest.lpszName     := PAnsiChar(AnsiString(FDataMessage.SendBCC));
MapiDest.ulRecipClass := MAPI_BCC;
end;

MapiLogon permet d'avoir une connexion avec le client de messagerie et MapiSendMail sert à envoyer le message.

// pour ajouter un accusé de lecture
// MapiMessage.flFlags :=  MAPI_RECEIPT_REQUESTED;

// pour ajouter des pièces jointes avec Fichier une variable de type PMapiFileDesc
// qui est initialisée à nil au début de cette procédure
// MapiMessage.nFileCount   := iNombreDeFichierAJoindre;
// Fichier                  := AllocMem(SizeOf(TMapiFileDesc) * MapiMessage.nFileCount);
// Fichier.nPosition        := 0;
// Fichier.lpszPathName     := PAnsiChar(AnsiString(email.ListePieceJointe[iCompte].sCheminDoc));
// MapiMessage.lpFiles      := Fichier;

// récupération du client de messagerie
MapiResult := MapiLogon(0, PAnsiChar(''), PAnsiChar(''), MAPI_LOGON_UI or MAPI_NEW_SESSION, 0, @MAPI_Session);

if (MapiResult = SUCCESS_SUCCESS)then
begin
// nous pouvons envoyer le message
MAPIError := MapiSendMail(0, 0, MapiMessage, MAPI_DIALOG or MAPI_LOGON_UI or MAPI_NEW_SESSION, 0);

// liste des erreurs en MAPI : http://support.microsoft.com/kb/119647
if MAPIError = SUCCESS_SUCCESS then
ShowMessage('Message envoyé avec MAPI.')
else
raise TMessageMAPIError.Create('Erreur MAPI avec le code ' + MapiResult.ToString + '.');
end
else
begin
raise TMessageMAPIError.Create('Erreur MAPI avec le code ' + MapiResult.ToString + '.');
end;
finally
MapiLogOff(MAPI_Session, 0, 0, 0);
FreeMem(MapiDest);
end;

VIII-B - Accusé de réception

Lors de l'envoi d'un message, nous pouvons demander un accusé de réception.

MapiMessage.flFlags :=  MAPI_RECEIPT_REQUESTED;

VIII-C - Ajouter une pièce jointe

Pour ajouter une pièce jointe, nous avons besoin d'une variable PMapiFileDesc. Pour réaliser le paramétrage de celle-ci, il faut indiquer le nombre de pièces à joindre.

MapiMessage.nFileCount   := iNombreDeFichierAJoindre;
Fichier                  := AllocMem(SizeOf(TMapiFileDesc) * MapiMessage.nFileCount);
Fichier.nPosition        := 0;
Fichier.lpszPathName     := PAnsiChar(AnsiString(email.ListePieceJointe[iCompte].sCheminDoc));
MapiMessage.lpFiles      := Fichier;

IX - Conclusion

Dans les applications, le SMTP est plus répandu que le MAPI, chaque méthode ayant ses avantages et ses inconvénients : ce sera à vous de choisir celle qui sera la plus adaptée à vos besoins.

La méthode SMTP permet d'envoyer rapidement les mails vers le serveur. En revanche, aucune copie des mails n'est sauvegardée. Il faut être en CC ou CCI pour garder une trace. Ce mode indépendant du client de messagerie permet d'envoyer des mails depuis une application sans configurer chacun des postes utilisateurs.

En MAPI (dont Outlook), il est possible d'envoyer et de recevoir des mails. Les envois sont enregistrés dans les messages envoyés et la plupart des clients fonctionnent même en hors-ligne en gérant une boîte d'envoi. Ce type de fonctionnement est moins répandu que le premier et la sécurité des clients de messagerie peut être un frein : il n'est pas rare de voir un popup apparaître lors de l'envoi d'un message depuis une application externe.

Quel que soit votre choix, vous avez vu que Delphi savait y répondre !