Envoyer des mails avec Delphi

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.

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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 :

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.

Image non disponible

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.

 
Sélectionnez
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.

 
Sélectionnez
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.

 
Sélectionnez
   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.

 
Sélectionnez
   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.

 
Sélectionnez
   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.

 
Sélectionnez
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.

 
Sélectionnez
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 :

VI-A. Envoyer un message texte

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

 
Sélectionnez
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.

 
Sélectionnez
   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.

 
Sélectionnez
      // 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.

 
Sélectionnez
      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.

 
Sélectionnez
      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.

 
Sélectionnez
   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 :

 
Sélectionnez
   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.

 
Sélectionnez
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.

 
Sélectionnez
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.

 
Sélectionnez
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électionnez
         // 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.

 
Sélectionnez
         // 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.

 
Sélectionnez
         // 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.

 
Sélectionnez
         // 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
 
Sélectionnez
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.

 
Sélectionnez
   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.

 
Sélectionnez
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.

 
Sélectionnez
      // 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.

 
Sélectionnez
      // 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.

 
Sélectionnez
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.

 
Sélectionnez
      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 !

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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.