Search code examples
delphiindydelphi-2007indy10

TIdSMTP : How to send special chars showing correctly in email body


I'm on Delphi 2007, Indy version 10.6.1.5188.

When I send messages using my regular SMTP server and TIdSMTP, everything works fine.

But when I send using Amazon Simple Email Service SMTP (SES), all special chars in msg body, like á, ç, é are replaced for strange symbols like ¿½ and .

What should I do to fix this issue, and why is it happening only when I use SES?

Here is my current code :

  idsmtp1.Host := 'email-smtp.us-west-2.amazonaws.com';
  idsmtp1.username := 'myusername';
  idsmtp1.password := 'mypassword';
  idsmtp1.Port := 587;
  idsmtp1.IOHandler := IdServerIOHandlerSSLOpenSSL1;
  idsmtp1.usetls := utUseExplicitTLS;
  idsmtp1.UseEhlo := true;
  idmessage1.body.text := 'This is a test é á ó ç';
  with IdServerIOHandlerSSLOpenSSL1 do
      begin
      SSLOptions.Method := sslvTLSv1;
      SSLOptions.VerifyMode := [];
      SSLOptions.VerifyDepth := 0;
      end;
  idsmtp1.Connect;
  idsmtp1.Send(idmessage1);

And here is TIDMessage.savetofile contents :

From: "My Company" <[email protected]>
Subject: Your subject
To: [email protected]
Bcc: [email protected]
Content-Type: text/plain; charset=us-ascii
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Sender: My Company <[email protected]>
Organization: My Organization
Date: Mon, 18 Nov 2019 09:19:05 -0300

VGhpcyBpcyBhIHRlc3Qgw6kgw6Egw7Mgw6cNCg==
.

Solution

  • Delphi 2007 is a pre-Unicode version of Delphi, where all of the strings used in Indy are AnsiString. As such, you need to manually encode your email text to an 8-bit encoding, such as UTF-8, and then set the email's ContentType and CharSet properties to match, as well as the ContentTransferEncoding property so the UTF-8 bytes can pass through 7-bit email systems without losing data. For example:

    IdSMTP1.Host := 'email-smtp.us-west-2.amazonaws.com';
    IdSMTP1.Username := 'myusername';
    IdSMTP1.Password := 'mypassword';
    IdSMTP1.Port := 587;
    IdSMTP1.IOHandler := IdServerIOHandlerSSLOpenSSL1;
    IdSMTP1.UseTLS := utUseExplicitTLS;
    IdSMTP1.UseEhlo := True;
    
    IdMessage1.Body.Text := UTF8Encode('This is a test é á ó ç');
    
    IdMessage1.ContentType := 'text/plain';
    IdMessage1.CharSet := 'utf-8';
    //alternatively
    // IdMessage1.ContentType := 'text/plain; charset=utf-8';
    
    IdMessage1.ContentTransferEncoding := 'base64';
    
    with IdServerIOHandlerSSLOpenSSL1 do
    begin
      SSLOptions.Method := sslvTLSv1;
      SSLOptions.VerifyMode := [];
      SSLOptions.VerifyDepth := 0;
    end;
    
    IdSMTP1.Connect;
    IdSMTP1.Send(IdMessage1);
    

    If you don't do this, then the email ends up being interpretted in other random charsets, such as US-ASCII, Windows-1252, etc which will not give you the results you want.

    If you ever upgrade your code to Delphi 2009 or later, where all of the strings used in Indy are UnicodeString, you still need the CharSet assignment, but you can drop the manual UTF8Encode() call:

    IdSMTP1.Host := 'email-smtp.us-west-2.amazonaws.com';
    IdSMTP1.Username := 'myusername';
    IdSMTP1.Password := 'mypassword';
    IdSMTP1.Port := 587;
    IdSMTP1.IOHandler := IdServerIOHandlerSSLOpenSSL1;
    IdSMTP1.UseTLS := utUseExplicitTLS;
    IdSMTP1.UseEhlo := True;
    
    IdMessage1.Body.Text := 'This is a test é á ó ç';
    
    IdMessage1.ContentType := 'text/plain';
    IdMessage1.CharSet := 'utf-8';
    //alternatively
    // IdMessage1.ContentType := 'text/plain; charset=utf-8';
    
    IdMessage1.ContentTransferEncoding := 'base64';
    
    with IdServerIOHandlerSSLOpenSSL1 do
    begin
      SSLOptions.Method := sslvTLSv1;
      SSLOptions.VerifyMode := [];
      SSLOptions.VerifyDepth := 0;
    end;
    
    IdSMTP1.Connect;
    IdSMTP1.Send(IdMessage1);