23 de novembro de 2012

Integrando o Outlook em aplicações Delphi - Parte II

No último post, eu mostrei os conceitos básicos envolvidos na integração do Outlook com aplicações Delphi. O exemplo apresentado lá abordou a manipulação dos contatos cadastrados no Outlook bem como a criação de novos contatos. Neste post eu abordo o aspecto mais utilizado da ferramenta, que é lidar com envio e recebimento de emails.

Enviar um email com o Outlook de dentro de um programa Delphi é uma operação relativamente simples. Assim como no caso dos contatos, o ponto de partida é objeto Outlook.Application. Aqui, no entanto, não é necessário trabalhar com Namespaces. Basta criar uma instância de MailItem (este é o tipo de objeto do Outlook que representa uma nova mensagem), fornecer-lhe informações relevantes para o envio e, então, efetivamente enviá-la. O código a seguir exemplifica esses passos:
procedure TForm1.btnEnviarClick(Sender: TObject);
var OutlookApp: TOutlookApplication;
email : MailItem;
begin
OutlookApp := TOutlookApplication.Create(Nil);

email := OutlookApp.CreateItem(olMailItem) As MailItem;
email.Subject := 'Envio automático';
email.BodyFormat := olFormatHTML;
email.HTMLBody := 'Este email é um <b>teste</b> para envio <b><span style="color:maroon;">automático</span></b>.';
email.Importance := olImportanceNormal;

email.Recipients.Add('balaiotecnologico@gmail.com');
if (email.Recipients.ResolveAll) then
email.Send
else begin
Application.MessageBox('Um ou mais destinatários não puderam ser resolvidos.'#13+
'Reveja a informação e tente novamente.'
, 'Alerta', MB_ICONWARNING);
email.Display(true);
end;

OutlookApp.Disconnect;
OutlookApp.Free;
end;
Veja que a criação da instância da mensagem é feita diretamente no objeto que representa a aplicação Outlook. A função CreateItem usada para esse fim é genérica e permite criar qualquer item dentro do Outlook. Assim, o valor olMailItem passado como parâmetro garante que o que está sendo criado é uma nova mensagem.

Após a criação, eu formato a mensagem fornecendo valores para propriedades relevantes como o assunto (Subject), a prioridade da mensagem (Importance), o texto dela (Body ou HTMLBody, dependendo do formato escolhido) e os destinários que a receberão (Recipients).

Opcionalmente, o método ResolveAll da lista de destinatários é chamado para verificar se os endereços incluídos são válidos. Em caso negativo, a própria tela de envio de email do Outlook é apresentada para que o usuário faça as correções necessárias antes de enviar o email manualmente.

Uma vez que a verificação foi feita com sucesso, a função Send é chamada para efetivamente fazer o envio. Por padrão, uma cópia da mensagem é armazenada na pasta Sent Items. Se não quiser guardar a cópia, use a propriedade DeleteAfterSubmit, ajustando-a para true. É possível também indicar outra pasta para guardar a cópia, modificando-se o valor da propriedade SaveSentMessageFolder.

Repare que não informei quem é o remetente da mensagem. Normalmente, o Outlook é configurado para se logar automaticamente quando a aplicação é executada, fazendo com que o remetente seja este usuário que fez o logon. É possível forçar outro logon por código, usando a função Logon de forma explícita e passando a ela as credenciais a serem utilizadas.

O código finaliza desconectando do Outlook e liberando a memória que esta sendo usada por ele.

Neste exemplo, passei o valor olFormatHTML na propriedade BodyFormat para permitir a inserção de código HTML diretamente em HTMLBody, o que flexibiliza a formatação da mensagem.

Embora eu não tenha tratado no código, é possível também incluir anexos no email. Um MailItem possui a propriedade Attachments para conter a lista de anexos. Antes do envio, podemos adicionar um ou mais anexos:
email.Attachments.Add('c:\ERP\abc71.jpg', EmptyParam, EmptyParam, EmptyParam);
O primeiro parâmetro é o caminho completo do arquivo a ser anexado. Os demais parâmetros são opcionais; passei EmptyParam a eles para que a função assuma valores internos padronizados para eles.

Para encerrar esse tópico, falo de um problema recorrente pra quem automatiza envio de email com o Outlook: a inclusão da assinatura padrão do usuário no corpo da mensagem. Da forma como foi exposto até aqui, apenas o texto incluído em HTMLBody será enviado, sem qualquer decoração extra que o usuário tenha configurado - como a assinatura ou um papel de carta. Isso acontece porque esses recursos não são inerentes ao MailItem mas sim ao editor associado a ele. Para acionar o editor, precisamos apenas invocar o método GetInspector. Um inspector é o responsável pela edição do item atual e, ao invocar o método, todo o ambiente para edição é criado, incluindo a formatação padrão para o item - neste caso, um email.

Isso preencherá o HTMLBody com um HTML completo, englobando a assinatura e outras formatações. Então, teremos que localizar a tag BODY desse HTML para acrescentar nosso texto personalizado no local correto sem perder a formatação padrão. O quadro abaixo mostra como obter esse efeito:
var { ... }
email : MailItem;
insp : Inspector;
idx: integer;
begin
{ ... }
insp := email.GetInspector;

idx := Pos ('<body', email.HTMLBody);
idx := PosEx ('>', email.HTMLBody, idx);
email.HTMLBody := MidStr (email.HTMLBody, 1, idx) +
'Este email é um <b>teste</b> para envio <b><span style="color:maroon;">automático</span></b>.' +
MidStr (email.HTMLBody, idx+1, Length(email.HTMLBody));

if (email.Recipients.ResolveAll) then
email.Send;
{...}
Nessa abordagem, apesar de o editor ser criado e sua configuração padrão ser respeitada, ele não é exibido para usuário. Se for necessário, chame o Display do MailItem para exibí-lo.

Comentei no post anterior mas não custa lembrar: todas as operações descritas neste post exigem que o Outlook esteja instalado no computador que for executá-las.

36 comentários :

Flávio Nascimento disse...

Boa tarde Luís,

Seus artigos são realmente excelentes, pensando nisso, gostaria de olhar se você poderia postar alguns exemplos sobre Collection e também sobre Property em delphi, são assuntos que tenho um pouco de dificuldade.

Att.
Flávio

Luís Gustavo Fabbro disse...

Flávio

Que tipo de dificuldade vc está tendo com as Collections e com Property?

[]s

Flávio Nascimento disse...

Eu Não consegui pensar em um exemplo prático para usar collections e propertys, ou seja, posso estar dando muitas voltas para desenvolver algo se soubesse como aplicar esse tipo de código. Se você puder pensar em algum tipo de aplicação para isso, seria de muita utilidade.

Flávio Nascimento disse...

Luís, se não for muito abuso, gostaria de pedir outro tipo de auxílio também. Peguei um projeto no meio em que um serviço vai chamar uma thread e essa thread vai chamar uma dll. No meio do desenvolvimento percebi que ou não é possível ou é feita de forma diferente o chamada da dll pela thread. Caso você conheça, tem como vc postar algum exemplo? Pois pesquisando na net não encontrei nada parecido. Mais uma vez, obrigado.
Flávio

Luís Gustavo Fabbro disse...

Flávio

Chamar funções de uma DLL em threads separadas não difere essencialmente de fazer a chamada na linha de execução principal do programa. O que pode acontecer é a função da DLL não estar preparada para funcionar em ambiente multi-thread. Neste caso, você terá que gerenciar as chamadas manualmente para evitar que elas executem em paralelo. Para isso, você pode usar Seções Críticas, como descrito no post sobre esse assunto.

No blog há outros posts sobre threads que podem ser úteis. Veja este link.

[]s

Flávio Nascimento disse...

Como sempre, excelente Luís. Parabéns

Flávio Nascimento disse...

Luís,

Em relação a property e collections, eu já vi em código de outros programadores, mas não consegui pensar numa utilidade para tal. Em quê isso pode ser útil na prática? Esse é o exemplo que não consegui pensar para te passar.
Flávio

Luís Gustavo Fabbro disse...

Flávio

Property é especialmente útil qdo se precisa reagir de imediato à alteração do valor de um membro de uma classe. Numa classe representando uma imagem, alterar sua altura, por exemplo, exigirá que você realoque o espaço interno que armazena a imagem, truncando-a ou inserindo um espaço em branco extra. O IDE do Delphi faz isso o tempo todo: qdo se altera uma propriedade visual de um componente (cor de fundo, por exemplo), o IDE imediatamente toma as providências para refletir a alteração.

Em relação aos collections, são implementações pré fabricadas de métodos tradicionais de organização de estruturas de dados. Cada um dos métodos tem sua própria utilidade e cenário onde são aplicáveis; pilhas, listas, vetores e árvores são exemplos mais comuns.

Att.

Flávio Nascimento disse...

Obrigado Luís, entendi a teoria. Mas assim como os demais exemplos práticos que você nos passa, teria como passar também exemplos de property e collections?

Att.
Flávio

Anônimo disse...

Bom dia,
Estou com problemas em implementar a integração com outlook como mencionado.

Ao instanciar a classe reporta erro " Classe não registrada".

O que pode ser ?

Luís Gustavo Fabbro disse...

Essa mensagem é reportada quando o componente não conseguiu encontrar as entradas do Outlook no registry do Windows. Pode ser que a instalação do Outlook no seu computador esteja corrompida (ela existe?) ou falte permissão de acesso para seu usuário.

Neste último caso, você poderá ter que solicitar o incremento no nível de execução do seu programa. Há um post no blog a esse respeito: Preparando aplicações Delphi para requerer incremento no Nível de Execução.

[]s

Luís Gustavo Fabbro disse...

Flávio

Dei início a uma série de posts para de Collections. O primeiro, sobre filas, já está publicado.

[]s

Anônimo disse...

Olá Luiz, Gostaria de saber se há possibilidade de recuperar os emails de outras pastas (ex.: Outbox, Sent Itens, etc...) utilizando POP, pois é assim com meu servidor e não pode ser modificado.

Obrigado pela atenção e parabens pelas ótimas dicas. Tem me ajudado muito.

Luís Gustavo Fabbro disse...

Veja no post Integrando o Outlook em aplicações Delphi - Parte I um exemplo de como capturar uma pasta usando o objeto Namespace. A função utilizada lá (GetDefaultFolder) aceita como parâmetro o tipo da pasta que você quer recuperar. Use, por exemplo, olFolderOutbox para a Caixa de saída ou olFolderSentMail para a caixa de emails enviados.

Usando diretamente o POP3 não é possível acessar outra pasta que não a de entrada pois esse protocolo não dá suporte ao sistema de pastas.

[]s

Tiago disse...

Olá Luís.
Sabe se é possível "suprimir" o pedido de confirmação de envio do Outlook? Mas sem precisar mexer nas opções do outlook diretamente por ele.
Querem que eu retire a mensagem, mas não querem que eu mexa nas configurações do Outlook por conta do seu uso normal.
Então imagino que se der, seria pelo Delphi.
Pode me ajudar? Grato!

Luís Gustavo Fabbro disse...

Tiago

Antes de enviar o email, você pode ajustar o valor das propriedades ReadReceiptRequested para controlar o recebimento de confirmação de leitura e OriginatorDeliveryReportRequested para controlar o recebimento de confirmação da entrega da mensagem ao(s) destinatário(s). Ambas são membros do objeto MailItem, usado no exemplo do post.

[]s

Unknown disse...

Luís, tenho uma dúvida. Existe alguma maneira de forçar a abertura do Outlook em si e não apenas a mensagem? O ocorrido é que o usuário acaba esquecendo de abrir o Outlook, fazendo com que o email fique parado na caixa de saída após o método Send. Ou talvez tenha alguma outra maneira de garantir o envio por completo?

Luís Gustavo Fabbro disse...

Rodrigo

O TOutlookApplication tem uma propriedade chamada ConnectKind para determinar como se dará a conexão com uma instância da aplicação. Por padrão, ela se conecta a uma instância existente do Outlook mas, se não houver uma, o programa é carregado automaticamente. Isso é, no seu caso o outlook já está sendo carregado, caso contrário o email não teria sido criado.

Você pode forçar o envio e recepção das mensagens pendentes usando a função SendAndReceive do Namespace.

[]s

Viní Ferrareze disse...

Boa tarde Luis, primeiramente parabéns pelo exemplo que voce passou, sou desenvolvedor de uma software house, estou com uma problema de envio de email, o problema é o seguindo as vezes o Outlook nao envia emails com ponto[.] antes do [@] ex: teste.123@gmail.com, a aplicação esta quase igual a sua ela da o Send tudo certo, so que dai vou olhar no Outlook e esta la um email que nao foi possivel enviar:
Não é possível entregar: testeee email,
Administrador de sistema

so acontece as vezes não é sempre, teria como me ajudar? obrigado

Luís Gustavo Fabbro disse...

Viní

Qual o texto completo do erro reportado pelo Outlook? Dependendo da mensagem, o problema pode estar no servidor do GMail, que estaria recusando seu email.

Caixa postal cheia é um exemplo de situação onde o email será recusado.

[]s

Anônimo disse...

Luís bom dia,
Implementamos a integração Delphi X Outlook e está funcionando 99%, um único problema é encontrado. Quando realizamos o envio, o Outlook abre uma janela de permissão de envio, no qual eu posso selecionar 1 min, 5 min, 10 min, etc. para permitir o envio do email. Sabe como resolver isso, ou seja, que não fique abrindo essa janela?

Obrigado,

Felipe Basso

Luís Gustavo Fabbro disse...

Felipe

Em que contexto essa janela de permissão está sendo exibida? Você configurou o usuário no Outlook para enviar emails em nome de outra pessoa?

[]s

Unknown disse...

Luís, parabéns pelo post, uma duvida tem como colocar outros e-mails em cópia?

Luís Gustavo Fabbro disse...

Lúcio

Veja a propriedade CC do MailItem.em http://msdn.microsoft.com/en-us/library/ff869030(v=office.14).aspx.

[]s

Claudio Ferreira disse...

Oi Luis excelente tópico. Mas estou com uma dificuldade. Qdo mando o email aparece uma tela de mensagem do Outlook dizendo que um programa está tentando enviar um email e precisa responder com Sim/Não e demora 5 segudos para sumir. Uso o Outlook 2003. Vocë sabe como desabilitar essa mensagem ?

Luís Gustavo Fabbro disse...

Cláudio

Isso está relacionado com as políticas de segurança do Outlook. Você pode desligar os alertas seguindo as instruções do post http://sogeeky.blogspot.com.br/2007/04/how-to-disable-outlook-security-warning.html. Porém, isso pode ser perigoso já que qualquer outro programa (um vírus, por exemplo), poderá acessar o outlook sem ser detectado.

Dependendo da forma como a segurança é tratada na empresa onde sua aplicação rodará, há a possibilidade de gerenciar esses acessos através da central de confiabilidade do Outlook. A melhor solução exigirá que você assine digitalmente sua aplicação e inclua o certificado na lista de fornecedores confiáveis do Oulook.

[]s

Claudio Ferreira disse...

Oi Luis valeu mas essa entrada no Registro é para quem usa Exchange Server, que não é meu caso. O meu Outlook é o 2003 ele não tem o conceito de central de confiabilidade, Isso foi criado no 2007

Unknown disse...

Luís, boa tarde...

Estou com dificuldade de utilizar o smtp...nós utilizamos outro smtp sem ser o padrão normal do outlook...

Rotina que utilizamos em VB:
Set oMensagem = CreateObject("CDO.Message")
Set oConfiguração = CreateObject("CDO.Configuration")

oConfiguração.Load -1 'Padrões CDO
Set vFields = oConfiguração.Fields
With vFields
.item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "relay.roll.br"
.item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
.item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
.item("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = 1
'.item("http://schemas.microsoft.com/cdo/configuration/sendusername") = " "
'.item("http://schemas.microsoft.com/cdo/configuration/sendpassword") = " "
.item("http://schemas.microsoft.com/cdo/configuration/smtpConnectionTimeout") = 45
.UPDATE
End With

Luís você tem alguma rotina parecida, mas em Delphi...?

Desde já agradeço!

Luís Gustavo Fabbro disse...

Fábio

O mecanismo pelo qual se resolve em tempo de execução as propriedades e funções de um objeto OLE é chamado de "late binding". Em Delphi, ele é obtido com variáveis do tipo OleVariant construídas com a função CreateOLEObject.

Por exemplo:

var oConfig: OleVariant;
begin
oConfig := CreateOLEObject('CDO.Configuration');
oConfig.Load (-1) //Padrões CDO
{ ... }
end;

[]s

Unknown disse...

Obrigado Luís....concluí aqui o envio de e-mail....

Unknown disse...

Boa tarde, Alguém tem exemplo de como integrar com o calendário do outlook?

Luís Gustavo Fabbro disse...

Leonardo

Que tipo de dificuldade você encontrou para acessar o calendário do Outlook usando as técnicas descritas no post?

[]s

Unknown disse...

Boa tarde, quando eu tento rodar este código com o outlook já aberto, dá erro na hora que ele faz o connect, como resolvo isso?

o Erro é:
"Falha na execução do servidor, ClassID: {0006F03A-0000-0000-C000-000000000046}."

Luís Gustavo Fabbro disse...

Anderson

Como é o código que você está usando pra conectar? Qual linha dá o erro? Com o outlook fechado seu programa funciona?

Há um exemplo de conexão no post inicial sobre integração com outlook (neste link). Com o código desse jeito também dá erro?

[]s

RAO Sistemas disse...

Bom dia, alguns dos meus clientes tem o office 2010... isso funciona mesmo que eu compile o programa em uma máquina que não possua esse office?

Luís Gustavo Fabbro disse...

Resposta curta: sim, funciona.

A solução se baseia na tecnologia COM, isto é, o programa na verdade usa uma identificação interna que o Windows usará para mapear os objetos do Office. Mesmo quando muda a versão, a maioria dos objetos mantém a identificação.

Com isso, você terá problemas somente se seu programa referenciar um tipo de objeto não suportado pela versão do Office presente no computador do usuário final.

[]s

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.