19 de maio de 2011

Enviando emails com Delphi - Parte III

Vida de programador é difícil... No início de 2010, o Google fez uma alteração no sistema online de email deles, o GMail, para substituir o método de segurança SSL então vigente no serviço pela sua versão mais nova, chamada TLS.

Por causa dessa modificação simples, muitos programas que antes conseguiam enviar email tranquilamente através do GMail falharam. Para piorar, a versão 9 do Indy não implementa nativamente o tratamento do TLS - isso só aconteceu a partir da versão 10. Ou seja, programas Delphi ou C++ Builder construídos com o Indy 9 não tinham uma solução fácil à mão para essa questão.

Na versão 10 do Indy, a solução é extremamente simples : basta configurar a propriedade UseTLS da instância de IdSMTP e manter as demais configurações:
IdSMTP1.UseTLS := utUseExplicitTLS;

Manter as demais configurações significa que o servidor deve continuar sendo smtp.gmail.com, a porta 587, com autenticação de usuário e senha. Também é obrigatório atribuir um handler para tratar a comunicação SSL/TLS. Para mais informações sobre isso e outros aspectos das configurações básicas, veja este post.

Embora não seja um processo tão direto, é possível trabalhar com TLS em Indy 9. Basicamente, é preciso enviar ao servidor de email um comando STARTTLS logo após o SMTP ter se conectado. O trecho abaixo é um código genérico que testa configurações internas para determinar se a conexão é segura. Em caso positivo, um handler é destacado para fazer o tratamento da conexão. Neste ponto, o SMTP já foi preparado com as configurações básicas necessárias.
if (TipoSeguranca = SEGURANCA_SSL) Or (TipoSeguranca = SEGURANCA_TLS) then
begin
IdSMTP.IOHandler := IdSSLIOHandlerSocket;
if (TipoSeguranca = SEGURANCA_TLS) then
begin
IdSSLIOHandlerSocket.SSLOptions.Method := sslvTLSv1;
IdSSLIOHandlerSocket.PassThrough := true;
end;
end;

{ Tenta conectar no servidor com 1 minuto de timeout }
IdSMTP.Connect (60000);

{ Emula o comportamento do TLS }
if (TipoSeguranca = SEGURANCA_TLS) then
begin
IdSMTP.SendCmd('STARTTLS', 220);
IdSSLIOHandlerSocket.PassThrough := false;
end;

if (Autentica And (not IdSMTP.Authenticate () ) then
Raise Exception.Create ('Falha na autenticação do usuário');

Ao detectar que a conexão segura exige TLS, alguns ajustes precisam ser feitos. Primeiro, mudo o método de segurança do handler para que adote o padrão para TLS. Depois, instruo o handler a codificar os comandos trocados entre ele e o servidor. Só então, podemos estabelecer a conexão.

Uma vez que a conexão está estabelecida, enviamos ao servidor o comando STARTTLS para ativar a comunicação segura na mesma porta. Por causa disso, o PassThrough que forçamos antes da conexão já pode ser desligado.

Autenticar manualmente o usuário e a senha é um preciosismo pois o método Send para envio da mensagem o fará por nós. Aqui no exemplo foi feita a autenticação explícita pra mostrar que é possível interceptar um erro de usuário/senha inválido antes de se tentar enviar a mensagem.

Agora, é só preparar uma instância do idMessage com os dados da mensagem (destinatários, assunto, corpo da mensagem, etc.) e enviá-la com o SMTP.

As considerações tecidas neste post também são válidas para quem usa o Hotmail como serviço para envio de email em suas aplicações. Há apenas duas diferenças importantes : o servidor de email, claro, é outro (smtp.live.com); também a forma de se especificar o usuário do serviço difere. No GMail, deve ser usado como nome do usuário somente o texto que vem antes do arroba (@) do endereço de email, enquanto no Hotmail o nome do usuário é o endereço completo.

Mais Informações
Enviando emails com Delphi - Parte I e Parte II, STARTTLS

9 comentários :

Anônimo disse...

Luís Gustavo, acompanho seu blog e gosto muito seus dos posts, pois sempre vem de encontro a alguma necessidade minha.
Em especial, este post de e-mail me ajudou bastante e gostaria de saber se vc tem algum exemplo ou poderia continuar essa série demonstrando como utilizar o Indy para assinar digitalmente meus emails, através de certificado digital.
e Mais uma vez.. parabéns

Luís Gustavo Fabbro disse...

Ainda não precisei trabalhar com esse tipo de cenário onde é preciso assinar um email. Vou dar uma pesquisada a respeito e, se encontrar uma solução com Indy, eu posto.

Abraço

Leonam Bernini disse...

Parabéns Luis Gustavo, gostei muito da sua explicação, você ensina detalhadamente, value, ajudou muito...

Fernando Tobias disse...

Parabéns pelo post.

Gostaria de tirar uma dúvida, pois montei o esquema para email do hotmail mas me rotorna este erro: "max line length exceeded".
Para o email do bol foi normalmente.

Luís Gustavo Fabbro disse...

Fernando

Em que momento o erro é reportado? Repliquei o exemplo usando uma conta do Homail e enviou sem problemas. Pode ser algum outro aspecto do seu programa que está falhando.

Você pode descobrir o ponto exato do erro colocando um dos componentes de log do Indy, disponíveis na guia "intercept".

[]s

Fernando Tobias disse...

Bom dia, primeiramente obrigado pelo retorno quase que imediato.

O erro ocorre nesta linha: IdSMTP1.Send(IdMessage1);

try
cancelar := false;
btnEnviarEmail.Enabled := false;
pg1.MaxValue := StrToInt(EdtNum.Text);
pg1.Progress := 0;
IdSMTP1.Host := edtHost.Text;
IDSMTP1.Username := edtUsuario.Text;
IdSMTP1.Password := edtSenha.Text;
IdSMTP1.Port := StrToInt(edtPorta.Text);

// servidor requer autenticacao
if ckbAutenticacao.Checked then
IdSMTP1.AuthenticationType:= atLogin
else
IdSMTP1.AuthenticationType:= atNone;

// utilizar conexao segura SSL
if ckbConexaoSSL.Checked then
IdSMTP1.IOHandler := IdSSLIOHandlerSocket1
else
IdSMTP1.IOHandler := nil;

//-- Utilizar Conexao Segura TSL
if (ckbConexaoTSL.Checked) then
begin
IdSSLIOHandlerSocket1.SSLOptions.Method := sslvTLSv1;
IdSSLIOHandlerSocket1.PassThrough := true;
end;

IdMessage1.MessageParts.Clear;

// adicionando anexos do email a ser enviado
if ListBoxAnexos.Items.Count > 0 then begin
for i:= 0 to ListBoxAnexos.Items.Count - 1 do
TIdAttachment.Create(IdMessage1.MessageParts, ListBoxAnexos.Items[i]);
end;

// dados da origem do email
IdMessage1.From.Address := edtRemetente.Text; //-- Email de Origem
IdMessage1.Subject := 'Assunto do Email';
IdMessage1.Body.Text := 'Corpo da Mensagem';

// dados do destino do email
IdMessage1.Recipients.EMailAddresses := edtEmailDestino.Text;
IdMessage1.BccList.EMailAddresses := '';
IdMessage1.CCList.EMailAddresses := '';

if NOT IdSMTP1.Connected then
IdSMTP1.Connect(2000);

if (ckbConexaoTSL.Checked) then
begin
IdSMTP1.SendCmd('STARTTLS', 220);
IdSSLIOHandlerSocket1.PassThrough := false;
end;

if IdSMTP1.Connected then begin
for i:= 1 to pg1.MaxValue do begin

IdSMTP1.Authenticate;
IdSMTP1.Send(IdMessage1);
Application.ProcessMessages;

if cancelar AND (Application.MessageBox('Deseja cancelar o processo?','Confirmação',mb_iconQuestion + mb_YesNo + mb_DefButton2) = mrYes) then
break;

cancelar := false;
pg1.AddProgress(1);
Application.ProcessMessages;
end;
end;

IdSMTP1.Disconnect;
btnEnviarEmail.Enabled:= true;
ShowMessage('E-mail Enviado com Sucesso!');
//Close;
Except
on E:Exception do begin
Screen.Cursor:= crDefault;
btnEnviarEmail.Enabled:= true;
ShowMessage(E.Message);
end;
end;

Luís Gustavo Fabbro disse...

Fernando

A única coisa realmente estranha no seu código é que o envio pode ser feito várias vezes com os mesmos parâmetros - esta parte está num laço FOR. Com minha versão do indy, são reportados erros a partir da segunda interação, embora diferentes do que vc reportou. Fazendo um único envio, funcionou ok.

Qual o objetivo desse laço ? Em certas situações, o primeiro envio pode deixar sujeira na instância da mensagem, ocasionando erros estranhos.

Não tentei anexar arquivos grandes pra ver se assim a mensagem que vc citou aparece.

[]s

Fernando Tobias disse...

Boa noite, Luis Gustavo.

Desculpe-me tomar seu tempo. Já achei o problema. Na verdade eu não estava setando o IdSMTP1.IOHandler := IdSSLIOHandlerSocket1 pois como estava utilizando o TSL achei que não era preciso por isso sempre deixava o checkbox sem "checar". Ai revisando o código e suas dicas no post verifiquei que eu estava setando errado, então fiz a alteração e funcionou blz.

Muito obrigado, pela atenção.

B.boy disse...

Cara muito bom o seu post, me ajudou muito, eu estava com problemas para configurar o hotmail, valeu mesmo

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.