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.
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 :
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 !