Easiest way to compose Outlook 2010 mail from Delphi? - delphi

Some of our applications which work fine with different ways of email integration, using mailto:, simulated "Send To...", and SMTP in Windows 2000 and 2003 environments, now move to a new Windows 2008 system with Exchange 2010 and Outlook 2010 clients.
We have one use case where the application creates a new mail, sets recipient(s) and subject, adds one or more attachments and then opens it in the default mail client so it can be edited by the user before sending.
Do you know a solution which works in the new environment? Should we use a third party library? Or is there some OLE automation code available which is known to work, using Outlook.Application?

We use JclSimpleBringUpSendMailDialog from the jclMapi unit in the Jedi JCL library.
I once had an app where we built in a user option to specify whether they wanted to use SMTP or MAPI and then all sorts of mail server settings but the Jedi library call makes life so much easier. If end users have gone to the trouble of setting up all their settings in a MAPI client then why would they want to set them all up again in my/our software.
The trouble with the mailto:// stuff is that it's often not quite configurable enough or the mail client doesn't handle the parameters in the same/standard way - then users think your software's rubbish rather than believe they have a dodgy mail client.
So we just use the MAPI interface. Easy.

I use this unit - it credits Brian Long ages ago...
unit UArtMAPI;
interface
procedure ArtMAPISendMail(
const Subject, MessageText, MailFromName, MailFromAddress,
MailToName, MailToAddress: String;
const AttachmentFileNames: array of String);
implementation
uses
SysUtils,
Windows,
UArtLibrary,
Dialogs,
Forms,
MAPI;
procedure ArtMAPISendMail(
const Subject, MessageText, MailFromName, MailFromAddress,
MailToName, MailToAddress: String;
const AttachmentFileNames: array of String);
//Originally by Brian Long: The Delphi Magazine issue 60 - Delphi And Email
var
MAPIError: DWord;
MapiMessage: TMapiMessage;
Originator, Recipient: TMapiRecipDesc;
Files, FilesTmp: PMapiFileDesc;
FilesCount: Integer;
begin
FillChar(MapiMessage, Sizeof(TMapiMessage), 0);
MapiMessage.lpszSubject := PAnsiChar(AnsiString(Subject));
MapiMessage.lpszNoteText := PAnsiChar(AnsiString(MessageText));
FillChar(Originator, Sizeof(TMapiRecipDesc), 0);
Originator.lpszName := PAnsiChar(AnsiString(MailFromName));
Originator.lpszAddress := PAnsiChar(AnsiString(MailFromAddress));
// MapiMessage.lpOriginator := #Originator;
MapiMessage.lpOriginator := nil;
MapiMessage.nRecipCount := 1;
FillChar(Recipient, Sizeof(TMapiRecipDesc), 0);
Recipient.ulRecipClass := MAPI_TO;
Recipient.lpszName := PAnsiChar(AnsiString(MailToName));
Recipient.lpszAddress := PAnsiChar(AnsiString(MailToAddress));
MapiMessage.lpRecips := #Recipient;
MapiMessage.nFileCount := High(AttachmentFileNames) - Low(AttachmentFileNames) + 1;
Files := AllocMem(SizeOf(TMapiFileDesc) * MapiMessage.nFileCount);
MapiMessage.lpFiles := Files;
FilesTmp := Files;
for FilesCount := Low(AttachmentFileNames) to High(AttachmentFileNames) do
begin
FilesTmp.nPosition := $FFFFFFFF;
FilesTmp.lpszPathName := PAnsiChar(AnsiString(AttachmentFileNames[FilesCount]));
Inc(FilesTmp)
end;
try
MAPIError := MapiSendMail(
0,
Application.MainForm.Handle,
MapiMessage,
MAPI_LOGON_UI {or MAPI_NEW_SESSION},
0);
finally
FreeMem(Files)
end;
case MAPIError of
MAPI_E_AMBIGUOUS_RECIPIENT:
Showmessage('A recipient matched more than one of the recipient descriptor structures and MAPI_DIALOG was not set. No message was sent.');
MAPI_E_ATTACHMENT_NOT_FOUND:
Showmessage('The specified attachment was not found; no message was sent.');
MAPI_E_ATTACHMENT_OPEN_FAILURE:
Showmessage('The specified attachment could not be opened; no message was sent.');
MAPI_E_BAD_RECIPTYPE:
Showmessage('The type of a recipient was not MAPI_TO, MAPI_CC, or MAPI_BCC. No message was sent.');
MAPI_E_FAILURE:
Showmessage('One or more unspecified errors occurred; no message was sent.');
MAPI_E_INSUFFICIENT_MEMORY:
Showmessage('There was insufficient memory to proceed. No message was sent.');
MAPI_E_LOGIN_FAILURE:
Showmessage('There was no default logon, and the user failed to log on successfully when the logon dialog box was displayed. No message was sent.');
MAPI_E_TEXT_TOO_LARGE:
Showmessage('The text in the message was too large to sent; the message was not sent.');
MAPI_E_TOO_MANY_FILES:
Showmessage('There were too many file attachments; no message was sent.');
MAPI_E_TOO_MANY_RECIPIENTS:
Showmessage('There were too many recipients; no message was sent.');
MAPI_E_UNKNOWN_RECIPIENT:
Showmessage('A recipient did not appear in the address list; no message was sent.');
MAPI_E_USER_ABORT:
Showmessage('The user canceled the process; no message was sent.');
SUCCESS_SUCCESS:
Showmessage('MAPISendMail successfully sent the message.');
else
Showmessage('MAPISendMail failed with an unknown error code.');
end;
end;
end.

Maybe this can be useful for you. But I can't test it, because I'm thunderbird user.

I dug up this example. It's from 2002, so uses an older outlook version, but the idea should still be the same. It was used in an application to send newly registered users their userid and password (don't start me on that, it was just a way to restrict access to a site, nothing sensitive there). The messages are saved in the Outbox, not opened, but if you dig through the OleServer and Outlook8 (number will be higher by now), you should find ways to open the mail for user attention instead of saving it straight to the outbox.
The entire Outlook object model can be found on msdn: http://msdn.microsoft.com/en-us/library/aa221870%28v=office.11%29.aspx
Another good source for information about Office Automation are Deborah Pate's pages: http://www.djpate.freeserve.co.uk/Automation.htm
function TStapWerkt_data.GenerateMailsOutlook(SendDBF: boolean): boolean;
var
Outlook: TOutlookApplication;
olNameSpace: NameSpace;
MailIt: TMailItem;
AttachedFile: OleVariant;
i: integer;
emailaddress: string;
begin
Result := true;
Outlook := TOutlookApplication.Create( nil );
try
Outlook.ConnectKind := ckNewInstance;
try
Outlook.Connect;
try
olNameSpace := Outlook.GetNamespace('MAPI');
olNameSpace.Logon('', '', False, False);
try
if SendDBF then begin
MailIt := TMailItem.Create( nil );
MailIt.ConnectTo( Outlook.CreateItem( olMailItem ) as MailItem );
try
MailIt.Recipients.Add( 'info#bjmsoftware.com' );
MailIt.Subject := 'StapWerk.dbf';
MailIt.Body := 'Stap'#13#10;
AttachedFile := IncludeTrailingBackslash( ExtractFilePath(
Stap_db.Stap_Database.DatabaseName ) ) + 'stapwerk.dbf';
MailIt.Attachments.Add( AttachedFile, EmptyParam, EmptyParam, EmptyParam );
MailIt.Save;
finally
MailIt.Free;
end;
end;
for i := 0 to FNewUsers.Count - 1 do begin
MailIt := TMailItem.Create( nil );
MailIt.ConnectTo( Outlook.CreateItem( olMailItem ) as MailItem );
try
emailaddress := TStapper( FNewUsers.Items[i] ).Email;
if emailaddress = '' then begin
emailaddress := C_MailUnknownAddress;
end;
MailIt.Recipients.Add( emailaddress );
MailIt.Subject := C_MailSubject;
MailIt.Body := Format( C_MailBody,
[TStapper( FNewUsers.Items[i] ).UserId,
TStapper( FNewUsers.Items[i] ).Password] );
MailIt.Save;
finally
MailIt.Free;
end;
end;
finally
olNameSpace.Logoff;
end;
finally
Outlook.Disconnect;
// We kunnen ondanks ckNewInstance quit niet gebruiken
// Outlook lijkt de connectkind te negeren en hoe dan ook te koppelen
// naar een bestaande instantie. En die gooi je dan dus ook dicht met quit.
// Outlook.quit;
end;
finally
Outlook.free;
end;
except
on E: Exception do begin
Result := false;
end;
end;
end;

Maybe this is the easiest way to open an email window
System("mailto:someone#example.com?Subject=Hello%20again&body=your%20textBody%20here")

Related

Collecting emails from Outlook Inbox with Delphi

I wrote some code to read emails from Outlook Inbox and collect attachments.
This is working just fine. I am using Outlook 2019/Office 365.
I can use both Mail.SenderEmailAddress or Mail.Sender.Address to get the email address of the sender.
When deploying my application to another computer with Outlook 2016, I get this error:
EOleError: Method 'Sender' not supported by automation object
Same for Mail.SenderEmailAddress
Outlook 2016 or 2019 have the same code stream. See Outlook versions, build numbers and other trivia.
Could you help understanding why I get such error on my client's computer and how can I fix that problem?
Are you aware of a free/commercial library or components that could do that smoothly?
This is somehow linked with my other question about Sending Outlook Email with Delphi.
try
Outlook:=GetActiveOleObject('Outlook.Application') ;
except
Outlook:=CreateOleObject('Outlook.Application') ;
end;
try
oNameSpace := Outlook.GetNamespace('MAPI');
oNameSpace.Logon;
Inbox:= oNameSpace.GetDefaultFolder(6);
iNbMail:= Inbox.Items.Count;
for i:= iNbMail downto 1 do
begin
if VarIsNull(Inbox.Items[i]) or VarIsEmpty(Inbox.Items[i]) then
Continue;
Mail:= Inbox.Items[i];
EmailAddress:= Mail.Sender.Address;
// EmailAddress:= Mail.SenderEmailAddress;
UnReadFlag:= Mail.UnRead;
iNbAttach := Mail.Attachments.Count;
for j := iNbAttach downto 1 do
begin
Attachment:= Mail.Attachments[j];
if ExtractFileExt(Attachment.FileName) = '.pdf' then
begin
SaveName:= TPath.Combine(InboxFolder, Attachment.FileName);
Attachment.SaveAsFile(SaveName);
end;
end;
Mail.UnRead:= False;
end;
finally
Outlook:= Unassigned;
oNameSpace:= Unassigned;
Inbox:= Unassigned;
Mail:= Unassigned;
end;
Inbox folder in Outlook is not restricted to contain only mail messages. When iterating through its items you can encounter various item classes like MailItem, PostItem, MeetingItem, TaskRequestItem and many others. All of these items support different sets of properties and not all of them have Sender, SenderEmailAddress or Attachments property. If you're interested only in mail items then you need to check item's Class property:
const
olMail = $0000002B;
{ ... }
for i := iNbMail downto 1 do
begin
Mail := Inbox.Items[i];
if Mail.Class <> olMail then
Continue;
{ here we can assume we're working with MailItem instance }
end;
I doubt that Outlook ever returns null or empty item, so the check you do in your code is pointless. If you're interested in other item classes then check out OlObjectClass enumeration.
I'm not sure why you prefer to use late-binding, because Delphi already comes with imported type library Outlook2010.pas to automate Outlook in strongly typed manner. The library is located in OCX\Servers subfolder of installation folder. In case you need to support pre-2010 Outlook versions you can use unit OutlookXP or even Outlook2000 instead. The code fore iterating mail items using the type library could look like this:
uses
System.SysUtils, System.Variants, Winapi.ActiveX, Outlook2010;
function GetOutlookApplication: OutlookApplication;
var
ActiveObject: IUnknown;
begin
if Succeeded(GetActiveObject(OutlookApplication, nil, ActiveObject)) then
Result := ActiveObject as OutlookApplication
else
Result := CoOutlookApplication.Create;
end;
procedure ProcessInboxItems;
var
Outlook: OutlookApplication;
Inbox: Folder;
Index: Integer;
LItems: Items;
LMailItem: MailItem;
begin
Outlook := GetOutlookApplication;
Outlook.Session.Logon(EmptyParam, EmptyParam, EmptyParam, EmptyParam);
Inbox := Outlook.Session.GetDefaultFolder(olFolderInbox);
LItems := Inbox.Items;
for Index := LItems.Count downto 1 do
begin
if Supports(LItems.Item(Index), MailItem, LMailItem) then
begin
{ do whatever you wish with LMailItem }
end;
end;
end;

Retrieve Outlook logged-in user SMTP address after connecting through OLE

Exchange Web Services has a ResolveNames() function that I can use to retrieve (among other things) the primary SMTP address for the Active Directory user that logged on to Exchange Server through EWS.
I am now programming through OLE against Outlook and would like the same functionality.
I have been browsing through the Outlook object model but can't find an appropriate object or method.
Does anyone know of an object/method that I can use to get the primary SMTP address?
Below is the current Delphi code that I use to connect to Outlook.
For the default user logging in (AUserSMTP='') it returns the OutlookApp COM Object (through GetActiveOleObject or CreateOleObject), a NameSpace (through GetNameSpace) and a Folder (through GetDefaultFolder) object, but I could not find where to go from there.
I thought lNameSpace.CurrentUser (a Recipient object) might lead somewhere, but its Address property only returns a string like '/o=TimeTell/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=developer' without email address...
Any suggestions about the route to take?
function TDataModuleSyncOutlook.ConnectToOutlook(AUserSMTP: String = ''): Boolean;
var
lNameSpace, lRecipient: OleVariant;
begin
Result := false;
FWasCreated := False;
try
FOutlookApp := GetActiveOleObject(scxOutlookApp);
Result := True;
except
try
FOutlookApp := CreateOleObject(scxOutlookApp);
FWasCreated := True;
Result := True;
except
on E:Exception do ...
end;
end;
if Result then
begin
lNameSpace := FOutlookApp.GetNameSpace(scxNameSpace);
if AUserSMTP <> '' then // This part not applicable to the question
begin // Open shared calendar als er een expliciete gebruiker is opgegeven...
lRecipient := lNameSpace.CreateRecipient(AUserSMTP);
try
FCalendarFolder := lNameSpace.GetSharedDefaultFolder(lRecipient, olFolderCalendar);
except
on E:Exception do ...
end;
end
else // ... anders de default calendar folder openen
FCalendarFolder := lNameSpace.GetDefaultFolder(olFolderCalendar);
end;
FOleInitialized := Result;
if Result then TSyncLogger.LogAlways('Connected to Outlook') else TSyncLogger.LogAlways('Connection to Outlook failed');
end;
Try to use Application.Session.CurrentUser.AddressEntry.GetExchangeUser.PrimarySmtpAddress (you would of course need to check for nulls).
As for the account order, you can either use Extended MAPI and IOlkAccountManager.GetOrder (you can play with that object in OutlookSpy (I am its author) if you click IOlkAccountManager button) or you can use Redemption (I am also its author) and its RDOSession.Accounts.GetOrder method (see http://www.dimastr.com/redemption/RDOAccounts.htm). The first account in the returned collection will be the default one.
I found it. I have to go through the Accounts object in the namespace:
for i := 1 to lNameSpace.Accounts.Count do
if lNameSpace.Accounts.Item[i].AccountType = olExchange then
begin
lAccount := lNameSpace.Accounts.Item[i];
Break;
end;
if VarIsClear(lAccount) then
begin
DisConnectFromOutlook;
Exit;
end;
lLoginSMTP := lAccount.SmtpAddress;
The only thing I would still like is to determine the default account.

SMTP.host variable issue

Here is my Issue. I'm using Delphi 5 and Indy 9. I have no option to upgrade either at the moment. I am trying to send an email via gmail, and i hard code the string as 'smtp.google.com' it works just fine. however, if smtp.host is getting the host name from a variable it fails with error 11001, and i havent been able to figure out why. I'm new using indy so i'm probably missing something silly, but i don't understand why it could accept a string as the host, but not a variable holding the string. (It's got to be a variable because i need to pass the procedure different SMTP hosts based on the user signed in. Here is the code:
procedure TFormEmail.SendSimpleMail(ToEmail, MyAttachment: string);
var
Msg: TIdMessage;
DestAddr: TIdEmailAddressItem;
SMTP: TIdSMTP;
SSLHandler : TidSSLIOHandlerSocket;
Attachment: TIdAttachment;
SMTPHost1 :string;
begin
Msg := idMessage1;
Msg.From.Text := EmailName(My_User);
Msg.From.Address := EmailAddress(My_User);
msg.Subject := 'Test';//email subject
DestAddr := Msg.Recipients.Add;
DestAddr.Text := '';//receiver's name (optional)
DestAddr.Address := ToEmail;//where its going
Msg.Body.Add(edtEmailBody.text); //email body
SMTP := IdSMTP1;
SMTP.IOHandler := idSSLIOHandlerSocket1;
SMTPhost1 := SMTPHost(My_User);
SMTPhost1 := 'smtp.google.com';
//SMTP.Host := SMTPhost1; //<--FAILS
SMTP.Host := 'smtp.google.com'; //<--SUCCEEDS
SMTP.Port := SMTPPort(My_User);
SMTP.AuthenticationType := atLogin; //error here (2 error)
SMTP.Username := EmailAddress(My_User);
SMTP.Password := SMTPPassword(My_User);
If not empty(MyAttachment) then
Attachment := TIdAttachment.Create(Msg.MessageParts, MyAttachment);//loads Att
Try
SMTP.Connect;
except
SMTP.Connect;//refire if exception (issue with INDY)
end;
if useSSL(My_User) then
SMTP.SendCmd('STARTTLS');//load TLS
SMTP.Authenticate;
SMTP.Send(Msg);
SMTP.Disconnect;//disconnect from server
end;
I marked the one that fails and the one that succeeds, but i don't understand what i'm doing wrong. Any help would be appreciated
Seems you have more issues in one question, I can only help you with one.
I had the same issue with Connect, I simply called the Load method from IdSSLOpenSSLHeaders.
Try the following:
SMTP := IdSMTP1;
IdSSLOpenSSLHeaders.Load;
SMTP.IOHandler := idSSLIOHandlerSocket1;

How to get body texts of all e-mail messages from a certain IMAP mailbox?

How to get body texts of all e-mail messages from a certain IMAP mailbox in Delphi ? For example, from the INBOX mailbox ?
There are many ways to retrieve all body texts of all messages from the selected mailbox. I've used the one, where you iterate the mailbox and Retrieve every single message from the mailbox one by one. This way allows you to modify the code, so that you'll be able to break the loop when you need or e.g. replace Retrieve by RetrievePeek which won't mark the message as read on server like the first mentioned does. When the message is retrieved from server, all its parts are iterated and when it's the text part, its body is appended to a local S variable. After the iteration the S variable is added to the output BodyTexts string list. So, as the result you'll get string list collection where each item consists from the concatenated message's text part bodies and where each item means one message.
uses
IdIMAP4, IdSSLOpenSSL, IdText, IdMessage, IdExplicitTLSClientServerBase;
procedure GetGmailBodyTextParts(const UserName, Password: string;
BodyTexts: TStrings);
var
S: string;
MsgIndex: Integer;
MsgObject: TIdMessage;
PartIndex: Integer;
IMAPClient: TIdIMAP4;
OpenSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
BodyTexts.Clear;
IMAPClient := TIdIMAP4.Create(nil);
try
OpenSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
try
OpenSSLHandler.SSLOptions.Method := sslvSSLv3;
IMAPClient.IOHandler := OpenSSLHandler;
IMAPClient.Host := 'imap.gmail.com';
IMAPClient.Port := 993;
IMAPClient.UseTLS := utUseImplicitTLS;
IMAPClient.Username := UserName;
IMAPClient.Password := Password;
IMAPClient.Connect;
try
if IMAPClient.SelectMailBox('INBOX') then
begin
BodyTexts.BeginUpdate;
try
for MsgIndex := 1 to IMAPClient.MailBox.TotalMsgs do
begin
MsgObject := TIdMessage.Create(nil);
try
S := '';
IMAPClient.Retrieve(MsgIndex, MsgObject);
MsgObject.MessageParts.CountParts;
if MsgObject.MessageParts.TextPartCount > 0 then
begin
for PartIndex := 0 to MsgObject.MessageParts.Count - 1 do
if MsgObject.MessageParts[PartIndex] is TIdText then
S := S + TIdText(MsgObject.MessageParts[PartIndex]).Body.Text;
BodyTexts.Add(S);
end
else
BodyTexts.Add(MsgObject.Body.Text);
finally
MsgObject.Free;
end;
end;
finally
BodyTexts.EndUpdate;
end;
end;
finally
IMAPClient.Disconnect;
end;
finally
OpenSSLHandler.Free;
end;
finally
IMAPClient.Free;
end;
end;
This code requires OpenSSL, so don't forget to put the libeay32.dll and ssleay32.dll libraries to a path visible to your project; you can download OpenSSL libraries for Indy in different versions and platforms from here.

How to show on front (and not on background) a new email form in Outlook with OLE?

I am using OLE with Delphi to communicate from my delphi app to Outlook.
I am opening the new email form in Outlook using the following code. The problem is that the form is on background, so if the form from which I am generating the email form is maximized it will "cover" the Outlook new mail form.
How can I make that form appear it on top? (not "sticking on top", but simply that it appears on top, then a user can mimimize it if they want).
This is the code:
try
OutlookApp := GetActiveOleObject('Outlook.Application');
except
OutlookApp := CreateOleObject('Outlook.Application');
end;
try
MailItem := OutlookApp.CreateItem(olMailItem);
MailItem.To := 'Test#mail.com';
MailItem.Subject := 'This is the subject';
MailItem.HTMLBody := '<HTML>Test</HTML>';
MailItem.Display;
finally
OutlookApp := VarNull;
end;
end;
Just add one more call:
MailItem.Display;
OutlookApp.ActiveWindow.Activate;
Activate brings Outlook to front.
The MailItem.Display has the optional parameter Modal which should make your window modal, so try to use:
MailItem.Display(True);
I realize that this is an old topic, but I had the same problem recently for users with Outlook 2016. For me, the solution needed to be able to include a signature and an attachment and open the new Outlook window on top. If you call MailItem.Display(True), you lose the attachment. This was the solution that worked for me.
Notice the extra space after "Message (HTML)" in the Window name. It took me a long time to discover that the window name for new HTML emails has an extra blank space at the end.
procedure DisplayMail(Address, Subject, Body: string; Attachment: TFileName);
var
Outlook: OleVariant;
Mail: OleVariant;
H : THandle;
TempTitle : String;
begin
TempTitle := 'Temp-'+IntToStr(Random(1000000));
try
Outlook := GetActiveOleObject('Outlook.Application');
except
Outlook := CreateOleObject('Outlook.Application');
end;
Mail := Outlook.CreateItem(olMailItem);
Mail.To := Address;
Mail.Subject := TempTitle;
Mail.Display(false);
H := FindWindow('rctrl_renwnd32',PWidechar(TempTitle+' - Message (HTML) '));
Mail.Subject := Subject;
if Body <> '' then
Mail.HTMLBody := InsertMessage(Body,Mail.HTMLBody);
if Attachment <> '' then
Mail.Attachments.Add(Attachment);
try
if H <> 0 then
SetForegroundWindow(H);
finally
end;
end;
This worked for me, and it allows me to add an attachment, and it retains the default signature. I hope this helps somebody.

Resources