E-Mail File Attachment Using MIME (with HTML support)

Environment: VC6 MFC

Introduction

When a codeguru "afficionado" wants to send automatic mail or needs a mailer for any reason, he may use the classes designed by Wes Clyburn under the title E-Mail file attachment using MIME. See there, where there is a good description of MIME mechanisms and a description of the classes used.

Anyway, there was the need for an update to these classes to add a few more features:

  • Use of HTML text with images.
  • Optimisation of speed due to the use of long strings.
  • Support of CC and BCC.
  • Progess notification for long transfers.
  • Support of BASE64 coding directly from memory (and not only from a file).

With these updates, a nearly full compatibility had been kept. "Nearly" means that the compatibility is at the source level, but the numeric values of some enums has been changed.

Installation

  • Unzip the temail project.
  • Update MYSELF, MAILSERVER, and FROM defines in temail.cpp to your own addresses
  • Compile, build and execute it
You will receive five HTML emails with image and attached file, or not.

Overview

A mail message may contain one or many elementary "parts" : HTML text, GIF image, attached file...

All mail messages are not MIME, but...

A MIME mail message (CMIMEMessage) is considered as having a main MIME part(CMIMEPart), and only one. Some MIME parts are considered as "containers" of some other "elementary" MIME parts. This was not the case with the original classes of Wes, where elementary MIME parts where added to the message itself: the concept of "container" was implicit but not formalized.

The containers are of type MIXED, ALTERNATIVE, or RELATED (Mmm... you are allowed to ignore all these kind of containers and simply copy the example).

The elementary MIME parts supported are of type TEXT_PLAIN, TEXT_HTML, APPLICATION_OCTETSTREAM (for attachment), APPLICATION_OCTETSTREAM_IMAGE (for embedded images)

Well, this should be enough to see an example.

Example

void TestHTMLMailWithGifWithAttach()
{
  // Create and initialize a message
  CMIMEMessage *pMsg= new CMIMEMessage; 
  pMsg->m_sFrom = FROM;
  pMsg->AddMultipleRecipients(MYSELF);
  pMsg->AddMultipleRecipients("john@brown.family",
                              CMailMessage::BCC);
  pMsg->m_sSubject = "Test CMIMEmessage";
  // Create MIME containers
  CMIMEMessage::CMIMEPart *pMIMEmixed =
        pMsg->AddMIMEPart(CMIMEMessage::MIXED);
  CMIMEMessage::CMIMEPart *pMIMErelated =
        pMIMEmixed->AddMIMEPart(CMIMEMessage::RELATED);
  CMIMEMessage::CMIMEPart *pMIMEalternative = 
      pMIMErelated->AddMIMEPart(CMIMEMessage::ALTERNATIVE);
  // Alternative 1 : mail client does not support HTML...
  //      tell it in plain text 7Bits (warning : no 
  //      conversion is done)
  CString Text(
"Text that appear when client does not support HTML\r\n\r\n");
  pMIMEalternative->AddMIMEPart(CMIMEMessage::TEXT_PLAIN,Text);
  // Alternative 2 : mail client does support HTML...
  //      tell it in HTML text quoted-printable (warning : 
  //      no conversion is done)
  CString Html;
  Html=GetHTMLResource(IDR_HTML1);
  pMIMEalternative->AddMIMEPart(CMIMEMessage::TEXT_HTML,Html);
  // Prepare GIF image
  char* Gif;
  int Len;
  GetGIFResource(IDR_TOLLOGO, &Gif, &Len);
  // GIFS are related to HTML text : note 
  //    the string "IDR_TOLLOGO" which appears somewhere
  //    in IDR_HTML1 text
  pMIMErelated->AddMIMEPart(
         CMIMEMessage::APPLICATION_OCTETSTREAM_IMAGE,
         Gif,
         CMIMEMessage::MEMORY,
         "IDR_TOLLOGO",
         Len);
  GetGIFResource(IDR_HR, &Gif, &Len);
  pMIMErelated->AddMIMEPart(
         CMIMEMessage::APPLICATION_OCTETSTREAM_IMAGE,
         Gif,
         CMIMEMessage::MEMORY,
         "IDR_HR",
         Len);
  // Add attachment
  pMIMEmixed->AddMIMEPart(
         CMIMEMessage::APPLICATION_OCTETSTREAM,
         ATTACHMENT);
  // Do not forget to...
  pMsg->FormatMessage();
  // Then ...
  SendSMTP(pMsg);
}

This example should be enough to solve most of the complex cases. On the contrary, simpler cases are possible. See temail.cpp for simpler examples.

Tips

Calling AddMIMEPart()

The parameters of AddMIMEPart() are:
  CMIMEPart* AddMIMEPart(eMIMETypeCode nContentType, 
     LPCTSTR szContent= NULL,
     eMIMEEncodingCode nEncoding = DEFAULT,
     LPCTSTR szParameters = NULL, 
     int Len=0);
  • nContentType is one of the values seen in the overview.
  • szContent may be the contents of the source of data or a file path to the source of data.
  • nEncoding is one of the following basic values: DEFAULT,_7BIT, QUOTED_PRINTABLE, BASE64. DEFAULT is very fine, but you can try other values. Please note that only BASE64 does effective encoding.
    Other bits may be or'ed with the basic value:
    • MEMORY or FILE indicate the source of data. In the case of FILE szContent contains the file name, else it contains the data itself.
    • SOONCODED and ENCODE indicate if data is soon coded or must be coded by AddMIMEPart.
    Please note the following limitations :
    • In TEXT_PLAIN and TEXT_HTML, BASE64, FILE and ENCODE are not implemented.
    • In APPLICATION_OCTETSTREAM and APPLICATION_OCTETSTREAM_IMAGE, the only implemented basic valueis BASE64. Moreover, SOON_ENCODED is not implemented.
    Please note the default values (when nEncoding is left to DEFAULT):
    • TEXT_PLAIN : _7BIT and SOONCODED and MEMORY.
    • TEXT_HTML : QUOTED_PRINTABLE and SOONCODED and MEMORY.
    • APPLICATION_OCTETSTREAM and APPLICATION_OCTETSTREAM_IMAGE : BASE64 and ENCODE and FILE.
  • szParameters may be used to add some text after the Content-Type except in the case of APPLICATION_OCTETSTREAM or APPLICATION_OCTETSTREAM_IMAGE. In this last case, it contains an identifier string for the image (cf IDR_TOLLOGO in the example).
  • Len indicates the length of szContent. In case of 0, a strlen is done.

Progress notification

Derive you own class from CMIMEmessage and override NotifyProgress(). For a basic example search CNotifiedMIMEMessage in temain.c.

To do

  • Test better : the mailer has been tested only with Outlook express 5.
  • No conversion are done except binary BASE64 for attached files and images.
  • Credits

    These classes come directly from the work of Wes Clyburn.

    Downloads

    Download source - 62 Kb
    Demo executable is not provided, because it effectively send emails...


    Comments

    • CSocket problem

      Posted by vgu on 11/29/2005 02:21am

      Hi, I am trying to use these classes in an extension dll, but I am having problems when trying to create the CSocket class (m_wsSMTPServer.Create()). Approximately each second time it fails. Any idea what it can be ?

      • CSocket problem

        Posted by pcouderc on 11/29/2005 05:18am

        1-I do not understand of which "Internal Server" you are speaking, of Web browser. There is no web browser in SMPT.
        2- Have you tried to open and close a simple CSocket . I fell the problem could be linked the socket layer.
        
        pierre(a t)couderc.cc

        Reply
      • CSocket problem

        Posted by vgu on 11/29/2005 05:01am

        Thank you for answering. When using your example everything works fine, but when putting the code into an extension dll it fails almost for every second email. It is the aforementioned method call that fails, something that creates an 'Internal server error 500' from the dll (in the web browser). I am not able to catch the error from the function. It may be some kind of cleanup/initialization thing.

        Reply
      • CSocket us

        Posted by pcouderc on 11/29/2005 02:53am

        I have got this SMTP.cpp from Wes Clyburn nearly without even opening the file...! This kind of problem has bever been reported, and I have few idea to suggest ; all I can say is that it is very robust code used since many years in production. Does the problem occur when you use the same code in a true good old exe? Maybe some cleaning at the end is not done correctly in CSMTP (or elswhere) in a way so that the problem appear only in a .dll, but is managed correclty in exe?

        Reply
      Reply
    • Tested under Mozilla and Thunderbird

      Posted by pcouderc on 12/09/2004 01:44am

      These classes have tested to generate messages that are read correctly too under Mozilla and Thunderbird Pierre Couderc http://www.tol.fr

      Reply
    • How can i read email that it has images or html

      Posted by Legacy on 01/15/2003 12:00am

      Originally posted by: Victor

      Hey
      
      who can help me ? when i receive a email , if it has images or html, i can't read it ! please give me a code examle

      Reply
    • I also want to use the default client with attachments

      Posted by Legacy on 01/07/2003 12:00am

      Originally posted by: David

      I cannot find any information on allowing a program to open a default email client with email addresses, names, body and attachments ready to go. Any help?

      Reply
    • Quoted Printable Encode / Decode Routine

      Posted by Legacy on 12/16/2002 12:00am

      Originally posted by: Bob Bonham

      I wrote a quoted printable decoder/encoder because I chose to open and read in my html files using CFile...they were html files that the user had made so he could select which template would be mailed, so I could not hard code the html in quoted-printable.
      
      

      Here is the code I wrote


      CString CQp::encode(CString target)
      {

      char a;
      CString at;
      CString place="";
      for(int i=0;i<target.GetLength();i++)
      {
      a=target.GetAt(i);

      if(a<32 && a!=10 && a!=13 && a!=9 || a==61 || a>126)
      {
      at.Format("%X",a);
      if(at.GetLength()==1)
      {
      at.Insert(0,"0");
      }
      at.Insert(0,"=");
      place.Insert(place.GetLength(),at);

      }
      else{
      place.Insert(place.GetLength(),a);
      }
      if(place.GetLength() % 70 == 0)
      place.Insert(place.GetLength(),"=\r\n");

      }
      return place;
      }


      CString CQp::decode(CString target)
      {

      char a;
      CString at;
      CString place="";
      char* s;
      char t;

      CString p2;
      for(int i=0;i<target.GetLength();i++)
      {
      a=target.GetAt(i);
      target.Replace("=\r\n","");
      if(a==61) //we have a control character
      {
      at=target.Mid(i+1,2); //00-FF we will assume always 2 chars in length
      t=strtol(at,&s,16);
      i+=2; //tell the counter we found it
      place.Insert(place.GetLength(),t);

      }
      else{
      place.Insert(place.GetLength(),a);
      }

      }
      return place;

      }

      Reply
    • Problem with these classes ( deadlock )

      Posted by Legacy on 11/05/2002 12:00am

      Originally posted by: Mihai Stanescu

      Hi
      
      

      My application is MFC. If i put in my application only the line CMIMEMessage *pMsg= new CMIMEMessage(); then something happens and my application doesnt terminate anymore(im running in debug now). It hangs.

      The thing is, if i remove, it works. I dont know if its these classes fault but im pretty nervous now so i had to write this.

      Rewrite2:
      I found your problem. You use in MIme type manager a critical section. I see a m_csAccess.Lock(); but i definitely dont see any unlock so it hangs on destructor. I took the liberty of adding some unlock calls.

      here is the modified code:

      CMIMEMessage::CMIMETypeManager::~CMIMETypeManager()
      {
      POSITION pos;
      CMIMEContentAgent* p;
      m_csAccess.Lock();
      pos = m_MIMETypeList.GetHeadPosition();
      while( pos != NULL )
      {
      p = m_MIMETypeList.GetNext( pos );
      delete p;
      }
      m_csAccess.Unlock(); // this is what i added
      }

      void CMIMEMessage::CMIMETypeManager::RegisterMIMEType(CMIMEContentAgent *pMIMEType)
      {
      ASSERT( pMIMEType != NULL );
      if( pMIMEType == NULL )
      return;
      m_csAccess.Lock();
      m_MIMETypeList.AddTail( pMIMEType );
      m_csAccess.Unlock(); // this is what i addded
      }

      CMIMEContentAgent* CMIMEMessage::CMIMETypeManager::GetHandler(int nContentType)
      {
      POSITION pos;
      CMIMEContentAgent* pType = NULL;

      m_csAccess.Lock();
      pos = m_MIMETypeList.GetHeadPosition();
      while( pos != NULL )
      {
      pType = m_MIMETypeList.GetNext( pos );
      if( pType->QueryType( nContentType ) == TRUE )
      break;
      }
      ASSERT( pType->QueryType( nContentType ) == TRUE ); // forget to register a new CMIMEContentAgent?
      m_csAccess.Unlock(); // this is what i added
      return pType;
      }

      If this was wrong sorry, its 2 am now.

      Mihai

      Reply
    • Bcc is not "blind" copy

      Posted by Legacy on 10/09/2002 12:00am

      Originally posted by: Andreas J�ger

      Hi,

      you should remove the following line, so that the Bcc does not appear in the header:
      if(!Bcc.IsEmpty()) add_header_line( (LPCTSTR) Bcc );
      Otherwise Bcc and Cc would be the same.

      Reply
    • RTF embedding

      Posted by Legacy on 09/30/2002 12:00am

      Originally posted by: Robert Dunn

      I have looked at these classes and as is they are great, however, I wish to add an agent to handle rich text embedding and I do not understand how that can be accomplished.

      This explanation of Wes' original code is good but I wonder if there can be other values for eMIMETypeCode other than those given. How do I get rich text format type? Do I make up my own type, add it to the enumeration, and write my own encoding and decoding agent or is the basic content type somehow functionaly limited to those listed here?

      Does anyone have an rft agent example?

      Reply
    • Outlook- multiple recipients format problem

      Posted by Legacy on 07/26/2002 12:00am

      Originally posted by: Andreas J�ger

      Outlook does not display a semicolon (";") between multiple recipients

      Reply
    • MINE attachment + MAPI?

      Posted by Legacy on 07/17/2002 12:00am

      Originally posted by: Tom Wang

      Is it possible to send MIME type attachments with
      MAPI? I want to mail a html file with images using
      the user's default email client, instead of our own.

      Reply
    • Loading, Please Wait ...

    Leave a Comment
    • Your email address will not be published. All fields are required.

    Top White Papers and Webcasts

    • Is your sales and operations planning helping or hurting your bottom line? Here are 5 useful tips from the experts at Quintiq to guide you to a better S&OP strategy.

    • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

    Most Popular Programming Stories

    More for Developers

    Latest Developer Headlines

    RSS Feeds