Leading your developers forward







Home

Programming

Web 
Development

E-Commerce

Web Services

Consulting

Mentoring

Training

Books &
Writing

 

 

 

ASP SendMail Component

If you've used HTML forms on a UNIX server with a PERL script, you probably used sendmail to send the form data by email. But if your forms are on an IIS server, there is no standard easy way to do this.We have created an Active Server Pages (ASP) component that sends e-mail from a web page. It works with any kind of mail software you are using that operates as an SMTP server. You can buy commercial packages to do this, but building our component is a simple way to learn the basics of putting together a component, and you'll have something useful.

The component properties include all the information needed to send an e-mail message, and are listed in the table that follows. Calling the SendMessage() method actually sends the message. If the e-mail was sent, SendMessage() returns TRUE and ErrorString is "MailSent". If there was a problem, SendMessage() returns FALSE and ErrorString is a text string explaining how the method failed.

Red Horizontal Line

Variable Description Sample Value (as in our Suggestion Box)
SMTPServer The SMTP Server we will connect to to send mail. www.gregcons.com
Same as LocalDomain: both IIS and SMTP Server are on the same machine
LocalDomain Used to introduce us in the SMTP "HELO" command, current machine's name and domain www.gregcons.com
Same as SMTPServer: both IIS and SMTP Server are on the same machine
From e-mail address message is to come from, used in SMTP "MAIL" command and in the message From header. webServer@gregcons.com
To e-mail address message is to go to, used in SMTP "RCPT" command and in the message To header. suggest@gregcons.com
FromName Name message is to come from, used in the message From header. gregcons Suggestion Box
ToName Name message it to go to, used in the message To header. Suggestion Box
Subject Subject of the message, used in the message Subject header. Suggestion From User
Message The message to be sent, lines separated by <CR><LF>'s <created from the form>
ErrorString Error message. Indicates how last SendMessage finished in a text format. Filled out by the SendMessage method.

Here's how to create the mail-sending ASP Component. If you don't want to follow all these steps, you can have a zip file of the DLL or a zip file of the project. But you'll learn more doing it yourself, and it won't take long at all. These instructions assume a certain familiarity with Visual C++ 5.0; if you've never used it before you might want to read Kate's Special Edition Using Visual C++ 5.0 or a similar introductory book. To build the component, follow these steps:

  1. Create the project. In the Visual C++ IDE select File, New. From the New Dialog select the Projects tab. Select ATL COM AppWizard from the listbox and set the project directory in the Location text box. Enter the name to create the project in the Project Name text box, I called mine GCSendMail. (see fig 1) Click OK.
  2. Go through the ATL COM AppWizard. In Step 1, Dynamic Link Library (DLL) should already be selected. Also select Support MFC. (see fig 2) I will use some of the MFC classes, especially CSocket, to make life easier. Normally for an ActiveX control you don't want the overhead of MFC in an ATL built control. But in this case the control runs on the server, not on the client machines, so the size of the MFC DLL is not an issue. If you prefer, you can make direct calls to the winsock DLL and eliminate MFC altogether. Now click Finish. The New Project Information dialog will come up summarizing the project. Click OK on this dialog and the project is created.
  3. Insert the object into the project. In the IDE, select Insert, New ATL Object.... The ATL Object Wizard dialog box will appear. In the left list box select Objects. In the right selection box select ActiveX Server Component. (see fig 3) Click Next. The ATL Object Wizard Properties dialog appears. The Names tab should be selected. You provide only the short name -- I used GCSMTP. You can adjust the other names, but I left them as suggested. (see fig 4)
  4. Accept the default attributes. Click the Attributes tab. The ASP documentation recommends Apartment or Both, preferably Both, for Threading Model. If your component would be placed in the Session object or the Application object, you should select Both here, and then thread-protect all the object variables. For this mail-sending object, which stores data for only one message, you don't want more than one thread using the object at the same time. Leave it as Apartment and save yourself some work. (see fig 5)
  5. Turn off optional methods. You have no need to access the intrinsic objects or to do anything when the page is started or ended, so select the ASP tab and deselect the OnStartPage/OnEndPage check box, which will grey out the intrinsic objects. (see fig 6) Click OK and the object will be added to the project.
  6. Add the properties to the object. In the ClassView, right click on the IGCSMTP interface and select Add Property... (see fig 7). The Add Property to Interface dialog box will appear. Under Property Type select BSTR and under Property Name put in SMTPServer. Leave the rest of the dialog alone. (see fig 8) and click OK. Repeat this for each property in the table above.
  7. Add the method. Again in ClassView, right click on the IGCSMTP interface and select Add Method.... In the Method Name box, put SendMessage. In the Parameters box, put [out, retval] long * lRetVal. (see fig 9) Click OK
  8. Add variables to the wrapper class. For each property you added in step 6, you will add a member variable to the class CGCSMTP. These variables store the property values. Each one has a name based on the name of the property, with the prefix m_bstr . They are all of type CComBSTR. To add them, in ClassView right click the CGCSMTP class and select Add Member Variable... from the menu. In the Add Member Variable dialog, in the Variable Type box, put CComBSTR and in the Variable Declaration box put the variable name. Select Private access. (see fig 10) Click OK. Repeat this for each property in the table above.
  9. Write the get and put functions. These are very similar for all the variables. Here is the code for the SMTPServer property:
    STDMETHODIMP CGCSMTP::get_SMTPServer(BSTR * pVal)
    {
      AFX_MANAGE_STATE(AfxGetStaticModuleState())
      *pVal = m_bstrSMTPServer.Copy();
       return S_OK;
    }
    
    STDMETHODIMP CGCSMTP::put_SMTPServer(BSTR newVal)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState())
       m_bstrSMTPServer = newVal;
       return S_OK;
    }

    Use the same code for each of the other properties, but change their names.

  10. Include the CSocket header. Switch to FileView and open stdafx.h and add the line "#include afxsock.h".

    At this point you have everything done except the SendMessage() method. To make sure you have not made any typos, you can compile the code at this point.

  11. Before you write SendMessage(), write two helper functions. CSocket::Send() does not guarantee that all the data passed will be sent; it may take several calls to Send() to send a large buffer. So SendMessage() uses a function called SendBuffer() that will not return until the whole buffer is sent. The same thing happens with CSocket::Receive(): you want to keep receiving until you have a whole line (SMTP responses are all one line long.) This second function is called GetLine(). Add the functions to CGCSMTP. Here's the code:

    int CGCSMTP::SendBuffer(CSocket & socket, const char * buffer, int len)
    {
    int amt;
    int amt_sent = 0;

    // if len is 0, buffer is null terminated string,
    // get its length
    if (len == 0)
    {
    len = strlen(buffer);
    }

    // keep sending data until it is all sent
    while (amt_sent < len)
    {
    amt = socket.Send(buffer + amt_sent, len - amt_sent);
    if (amt <= 0)
    {
    // error, not all sent
    amt_sent = amt;
    break;
    }

    amt_sent += amt;
    }

    return amt_sent;
    }

    int CGCSMTP::GetLine(CSocket & socket, char * buffer, int len)
    {
    int amt_read = 0;
    int amt;

    // set buffer to nullstr
    buffer[amt_read] = 0;

    // keep receiving until we have a complete line
    while ( strchr(buffer, '\n') == NULL)
    {
    amt = socket.Receive(buffer + amt_read, len-amt_read);
    if (amt <= 0)
    {
    // error, we do not have a complete line
    amt_read = 0;
    break;
    }

    amt_read += amt;
    buffer[amt_read] = 0;
    }

    return amt_read;
    }

  12. Code SendMessage(). This follows the steps laid down in RFC 821, which describes how to talk to an SMTP server. The process is simple: you connect to the SMTP server, send the HELO command, then the MAIL command, then the RCPT command or commands, then the DATA command followed by the message ending with a line that only contains a '.'. Then the QUIT command is sent to end the session. (If you're not familiar with RFCs and sockets programming, you might want to read a book like Kate's Building Internet Application with Visual C++.) Here's the code:
    STDMETHODIMP CGCSMTP::SendMessage(long * lRetVal)
    {
    	USES_CONVERSION;
    
    #define BUFFER_LEN 500
    	char buffer[BUFFER_LEN];
    	*lRetVal = FALSE;
    
    	AFX_MANAGE_STATE(AfxGetStaticModuleState())
    
    	// working string
    	CString str;
    
    	// socket to use for session with SMTP server
    	CSocket SMTPSocket;
    
    	// create SOCK_STREAM socket
    	if (!SMTPSocket.Create())
    	{
    		int err = SMTPSocket.GetLastError();
    		CString error;
    		error.Format(_T("Socket failed to create. err = %d."), err);
    		m_bstrErrorString = error;
    		return S_FALSE;
    	}
    
    	// connect to SMTP server (port 25)
    	str = W2A(m_bstrSMTPServer);
    	if (!SMTPSocket.Connect(str, 25))
    	{
    		int err = SMTPSocket.GetLastError();
    		CString error;
    		error.Format(_T("Host is unreachable. err = %d."), err);
    		m_bstrErrorString = error;
    		return S_FALSE;
    	}
    
    
    	if (GetLine(SMTPSocket, buffer, BUFFER_LEN) <= 0)
    	{
    		m_bstrErrorString = _T("Connect failed.");
    		return S_FALSE;
    	}
    
    	str.Format(_T("HELO %s\r\n"), W2A(m_bstrLocalDomain));
    	if (SendBuffer(SMTPSocket, str, 0) <= 0)
    	{
    		m_bstrErrorString = _T("Send HELO failed");
    		return S_FALSE;
    	}
    
    	if (GetLine(SMTPSocket, buffer, BUFFER_LEN) <= 0)
    	{
    		m_bstrErrorString = _T("Response from Send HELO failed");
    		return S_FALSE;
    	}
    
    	// ensure we get a 2xx message back
    	if (buffer[0] != '2')
    	{
    		m_bstrErrorString = 
       _T("This server rejects your site name. ");
    		return S_FALSE;
    	}
    
    	str.Format(_T("MAIL FROM: <%s>\r\n"), W2A(m_bstrFrom));
    	if (SendBuffer(SMTPSocket, str, 0) <= 0)
    	{
    		m_bstrErrorString = _T("Send MAIL FROM: failed");
    		return S_FALSE;
    	}
    
    	if (GetLine(SMTPSocket, buffer, BUFFER_LEN) <= 0)
    	{
    		m_bstrErrorString = _T("Response from Send MAIL FROM: failed");
    		return S_FALSE;
    	}
    
    	// ensure we get a 25x message back
    	if (buffer[0] != '2' || buffer[1] != '5')
    	{
    		m_bstrErrorString = 
    _T("This server rejects mail from you. ");
    		return S_FALSE;
    	}
    	
    	str.Format(_T("RCPT TO: <%s>\r\n"), W2A(m_bstrTo));
    	if (SendBuffer(SMTPSocket, str, 0) <= 0)
    	{
    		m_bstrErrorString = _T("Send RCPT TO: failed");
    		return S_FALSE;
    	}
    
    	if (GetLine(SMTPSocket, buffer, BUFFER_LEN) <= 0)
    	{
    		m_bstrErrorString = _T("Response from Send RCPT TO: failed");
    		return S_FALSE;
    	}
    	
    	// again make sure we get a 25x message back
    	if (buffer[0] != '2' || buffer[1] != '5')
    	{
    		m_bstrErrorString = 
    _T("This server rejects mail to that user.\n ")
    _T("Enter the correct userid in the compose view.");
    		return S_FALSE;
    	}
    
    	if (SendBuffer(SMTPSocket, "DATA \r\n", 0) <= 0)
    	{
    		m_bstrErrorString = _T("Send DATA failed");
    		return S_FALSE;
    	}
    
    	if (GetLine(SMTPSocket, buffer, BUFFER_LEN) <= 0)
    	{
    		m_bstrErrorString = _T("Response from Send DATA failed");
    		return S_FALSE;
    	}
    
    	str.Format(_T("From: %s <%s>\r\n")
    		       _T("To: %s <%s>\r\n")
    			   _T("Subject: %s\r\n")
    			   _T("\r\n")
    			   _T("%s"), 
    			   W2A(m_bstrFromName), W2A(m_bstrFrom),
    			   W2A(m_bstrToName), W2A(m_bstrTo),
    			   W2A(m_bstrSubject), W2A(m_bstrMessage));
    
    	// if Message does not end "\r\n" add one on
    	if (str.Right(2) != _T("\r\n"))
    	{
    		str += _T("\r\n");
    	}
    
    	// add .\r\n to indicate end of message
    	str += _T(".\r\n");
    
    	if (SendBuffer(SMTPSocket, str, 0) <= 0)
    	{
    		m_bstrErrorString = _T("Send message failed");
    		return S_FALSE;
    	}
    
    	if (GetLine(SMTPSocket, buffer, BUFFER_LEN) <= 0)
    	{
    		m_bstrErrorString = _T("Response from Send message failed");
    		return S_FALSE;
    	}
    	
    
    	if (SendBuffer(SMTPSocket, "QUIT\r\n", 0) <= 0)
    	{
    		m_bstrErrorString = _T("Send QUIT failed");
    		return S_FALSE;
    	}
    
    	if (GetLine(SMTPSocket, buffer, BUFFER_LEN) <= 0)
    	{
    		m_bstrErrorString = _T("Response from QUIT failed");
    		return S_FALSE;
    	}
    
    	m_bstrErrorString = _T("Mail Sent.");
    
    	*lRetVal = TRUE;
    
    
    	return S_OK;
    }
  13. Now you can compile a release version. I compiled a MinDependency version to reduce the number of DLL's needed on the server. Then copy the GCSendMail.DLL and any DLL's it uses to the IIS server machine. You can use "dumpbin GCSendMail.dll" to determine what DLL's it uses. Once it is moved to the server you can register it using "regsvr32 GCSendMail.dll". You are now ready to use the component in any of your ASP pages. We have used it in our suggestion box. Since this is a server side component, when you come to the page with a browser and choose View, Source you will not see the original ASP HTML source. We've provided it on a source page that also contains a brief description of how the page works.

    From Here

    There are some ways you could extend or improve on this code.

     

    • allow multiple To's, cc's and bcc's
    • improve the error checking.
    • if the user enters a '.' in the message text on a line by itself, it will end the message. SendMessage() should find any lines that are only '.'s and change them to '..'s.
 
Copyright Gregory Consulting Limited 1996-2007. All rights reserved.