|
|
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.
|
|
|
| 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:
- 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.
- 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.
- 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)
- 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)
- 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.
- 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.
- 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
- 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.
- 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.
- 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.
- 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;
}
- 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;
}
-
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.
|
|
|