SMTP

Jump to: navigation, search

Table of Contents >> Optional Protocols


The SMTP client allows the user to send emails through the SMTP server. The API includes functions for session control, connection control, and sending messages.

Session Control

A Session holds the client information, sever information and the user options.

User calls this API to allocate an SMTP session with the user specified options and registers the callback function. Returns a SMTP handle. Multiple emails could be sent within the same session.
Free the session data structure. If the session is connected, it'll be disconnected before be freed. The session handle can't be used any more. Using a closed session handle could cause undefined result.
These APIs can be used to set and get the session options.
These functions allow you to attach and fetch your own data to/from a SMTP handle. This may be useful for maintaining separate state information in callback functions if you are running concurrent SMTP clients.

Connection Control

Mail can be sent only when the session is connected.

This is a wrapper function for setsockopt() that allows direct modification of the client socket associated with a SMTP handle. Socket options must be set prior to connecting to the SMTP server.
Connect to the SMTP server.
Disconnect from the SMTP server.
When connected, if the user wishes to abort the action, they can call this function to send a RSET command to the server. The session is still connected after this command has been sent.
SMTP servers usually disconnect if a session is idle for too long. Sending a no operation command periodically can tell you if the server is still alive and may allow an idle session to remain connected longer.

Sending Messages

Specify the sender information.
Specify TO, CC, BCC. This could be called multiple times to send the same mail to multiple recipients.
Indicate the beginning of a mail message.
Send the actual message body, could be called multiple times to aggregate a large mail message.
Indicate the end of a mail message.

Sending a MIME message

  1. Use the MIME API to construct a MIME message and adds parts into it as either inline contents or attachments.
  2. Set the TM_SMTP_FLAG_MIME when calling tfSmtpUserSendBom().
  3. Get the MIME header by calling tfMimeGetHeader() and send it out by calling tfSmtpUserSendBody().
  4. Get the encoded buffers out of the MIME engine by calling tfMimeGetBody(), and send them as an email body by calling tfSmtpUserSendBody() until tfMimeGetBody() returns zero.
Note Note: The MIME engine is an independent module. Please refer to MIME for more information.

Blocking Mode

The user should call the following APIs in the following order to send a mail message when running in blocking mode. This is the order that the SMTP server expects. If successful, this sequence may be repeated without reconnecting. If you are running multiple threads, please note that these functions should be called from the same thread in which the SMTP connection was made (you may have separate SMTP connections in separate threads).

  1. tfSmtpUserSendSenderInfo()
  2. tfSmtpUserSendRecipientInfo()
  3. tfSmtpUserSendBom()
  4. tfSmtpUserSendBody()
  5. tfSmtpUserSendEom()

Non-blocking Mode

If you choose non-blocking mode, SMTP calls do not wait for a success or failure response from the server before returning to the user. After calling tfSmtpUserConnect(), you need to call tfSmtpUserExecute() periodically to check the completion status of last user action.

Information will be returned from tfSmtpUserExecute() and sent to the callback function you specified via tfSmtpUserNewSession(). This information tells you when you can proceed with the API calls. Follow the same sequence described in Blocking Mode above.

In non-blocking mode, you can call SMTP API functions from your user callback function or from the same code that calls tfSmtpUserExecute().

In the callback function, events should be processed as follows:

Event Your Action
TM_SMTP_EVENT_CONNECTED Call tfSmtpUserSendSenderInfo().
TM_SMTP_EVENT_SENDER_ACK Call tfSmtpUserSendRecipientInfo().
TM_SMTP_EVENT_RECIPIENT_ACK Call tfSmtpUserSendBom() (or tfSmtpUserSendRecipientInfo() to provide additional recipients).
TM_SMTP_EVENT_BOM_ACK Call tfSmtpUserSendBody() zero or more times, then call tfSmtpUserSendEom().
TM_SMTP_EVENT_EOM_ACK Call tfSmtpUserDisconnect() (or tfSmtpUserSendSenderInfo() to send again).
TM_SMTP_EVENT_DISCONNECTED Notify foreground code that the mail message was sent successfully and the connection is closed.
TM_SMTP_EVENT_SERVER_ERROR Notify foreground code that a permanent error occurred (i.e. server replied with a 5xx response code). If the server rejects one of your recipients with this event and you have more recipients, call tfSmtpUserSendRecipientInfo() with the next email address.
TM_SMTP_EVENT_SERVER_RETRY Notify foreground code that a temporary error occurred (i.e. server replied with a 4xx response code). If the server rejects one of your recipients with this event and you have more recipients, call tfSmtpUserSendRecipientInfo() with the next email address.
TM_SMTP_EVENT_TIMEOUT Notify foreground code that the operation has timed out. The connection will be closed. Call tfSmtpUserConnect() to reconnect.
TM_SMTP_EVENT_AUTH_OK Notify foreground code that the SMTP server has accepted your login credentials.
TM_SMTP_EVENT_AUTH_FAIL Notify foreground code that the SMTP server has rejected your login credentials. Authentication happens during connection. Call tfSmtpUserDisconnect() then tfSmtpUserConnect() to reconnect with different credentials.
TM_SMTP_EVENT_SECURE_OK Notify foreground code that SSL/TLS negotiation with the SMTP server has succeeded and the SMTP session is secure.
TM_SMTP_EVENT_SECURE_FAIL Notify foreground code that SSL/TLS negotiation with the SMTP server has failed and the SMTP session is not secure.
TM_SMTP_EVENT_RESET Call tfSmtpUserSendSenderInfo() to send again. This event is a response to a tfSmtpUserReset() call.
TM_SMTP_EVENT_NOOP_ACK Notify foreground code that the server is OK. This event is a response to a tfSmtpUserSendNoop() call.

 

Performance concerns when using non-blocking mode

Unlike blocking mode, which retains control and responds immediately to events, non-blocking mode can service events only when you call tfSmtpUserExecute(). If you have many messages to send or many recipients to include, the entire session can take more than a few seconds. This section explains how to maximize performance under these conditions.

First, an explanation. The lock-step nature of SMTP requires that each end send something then wait for the other end to send something before sending again. To send to 100 recipients the SMTP client must send 100 RCPT commands and the server must send 100 replies for a total of 200 exchanges back and forth. To make matters worse, the Treck SMTP client is limited in how quickly it can turn the exchange around. If you choose to call tfSmtpUserExecute() every 200ms, Treck will probably receive a server reply and notify you (TM_SMTP_EVENT_RECIPIENT_ACK) at that rate, which means your 100 calls to tfSmtpUserSendRecipientInfo() will take 20 seconds to complete before you even get to send your message.

To help speed things up, you need to know when to call tfSmtpUserExecute() and that is dictated by two asynchronous events: socket events and timer events. To enable asynchronous SMTP event notification, uncomment the following compile time macro in your trsystem.h.

#define TM_USE_SMTP_ASYNC_CB

Next, create a callback function using the following code as an example. Parameter smtpClientHandle is the SMTP session handle receiving the event. Parameter userPtr is the same value that tfSmtpUserGetPointer() would return. Parameter eventId is currently unused. The call to KernelSignal() is just an example of how one might signal the main thread to wake up and call tfSmtpUserExecute(). Note: you must not call tfSmtpUserExecute() or any other SMTP API function from your callback, unless stated otherwise (see below).

void mySmtpAsyncNotify(
    ttSmtpClientHandle  smtpClientHandle,
    void TM_FAR *       userPtr,
    int                 eventId)
{
    KernelSignal(mySmtpAsyncSemaphore);
}
Warning Warning: The SMTP asynchronous event callback can occur in the receive task or timer task (or possibly others). You must not call any SMTP API functions from this user callback function (with exceptions listed below). Doing so is not supported and will produce unpredictable results. You should also avoid any calls that could block the calling context, as this could degrade performance of the entire Treck stack.

Register your new callback function by calling tfSmtpUserSetAsyncCB(). If you have concurrent SMTP sessions, you can assign different functions to different handles. You will not receive asynchronous events on SMTP handles that have no callback registered.

tfSmtpUserSetAsyncCB(mySmtpClientHandle, mySmtpAsyncNotify);

This next step is optional. Whenever a user action (e.g. connect or SMTP command) is in progress and awaiting server response, there is a timer associated with the operation. You specify the timeout when you call tfSmtpUserNewSession().

The catch is, the timeout event is detected and serviced during your tfSmtpUserExecute() call. So, you may well ask, "how do I know when a timeout will occur so I can call tfSmtpUserExecute() to get the timeout notification?" Good question. If you are calling tfSmtpUserExecute() periodically and when you get an asynchronous event callback, then you are probably OK. Timeouts are usually specified in minutes, so an extra second or so shouldn't make a difference.

If you are not calling tfSmtpUserExecute() periodically, relying solely on asynchronous event callbacks, then you could be in for a long wait if the server has stopped responding. Use the tfSmtpUserReadTimer() function, as in the example below, to discover how much time (in milliseconds) before the operation in progress is due to expire. tfSmtpUserReadTimer() returns 0 if the operation has expired; it returns -1 if there is no operation awaiting server response or the SMTP handle is not valid.

while (!finished)
{
    msec = tfSmtpUserReadTimer(mySmtpClientHandle);
    if (msec > 0)
    {
/* An operation is in progress; wait for async signal or timeout */
        KernelWait(mySmtpAsyncSemaphore, msec);
    }
    else if (msec != 0)
    {
/* No operation is in progress or has expired; sleep for 1 second */
        sleep(1000L);
    }
    tfSmtpUserExecute(mySmtpClientHandle);
}

The following SMTP functions can safely be called from your asynchronous event callback function. All other calls will behave unpredictably.

Retrieving a Response from the STMP Server

The user should call tfSmtpUserGetLastServerResponse() to retrieve the last response received from the SMTP server. Status codes can be found in RFC 5321. The response message is updated after each of the following calls:

Using SMTP with SSL

RFC 3207 defines a method for using SMTP over Transport Layer Security for securing the conversation between the SMTP client and server.

Treck supports SMTP over SSL/TLS. To enable this feature, uncomment TM_USE_SMTP_SSL and TM_USE_SMTP in trsystem.h.

You will need to obtain an SSL session identifier by calling tfSslNewSession(). You may also need create some certificates for server and/or client validation. Please refer to the SSL Programmer's Reference for details.

Once you have your SSL session identifier and the SMTP client handle you obtain by calling tfSmtpUserNewSession(), you can associate the two by calling tfSmtpUserSetSsl(). This allows the Treck SMTP client to negotiate the terms of the SSL/TLS session with the SMTP server based on how you have configured your SSL client session. If you do not call tfSmtpUserSetSsl(), the Treck SMTP client will not attempt to secure the SMTP session.

If you call tfSmtpUserSetSsl() with a valid SSL session identifier, you may then select from one of several methods for securing the SMTP session:

Mode Action
TM_SMTP_TLSMODE_OPTIONAL Begin SSL/TLS negotiations only if the SMTP server advertises support for the STARTTLS command. If the server supports explicit Transport Layer Security, it will list the STARTTLS keyword in the initial EHLO response. This is the default mode when a valid SSL session identifier is provided.
TM_SMTP_TLSMODE_ALWAYS Always attempt explicit SSL/TLS negotiations, even if the SMTP server does not advertise support for the STARTTLS command. Use this mode when you unconditionally require a secure SMTP session if, for example, you need to send login credentials and you do not wish to send them in plaintext. This mode will thwart a man-in-the-middle attack that strips the STARTTLS keyword from the server's EHLO response.
TM_SMTP_TLSMODE_IMPLICIT Unconditionally begin SSL/TLS negotiations immediately after connecting with the SMTP server. This is known as implicit SSL/TLS. It is a non-standard mode of operation that was mentioned in Appendix A of the The SSL Protocol Version 3.0 for specific use with servers that accept SMTP connections on port 465.

You can change the SSL/TLS mode by calling tfSmtpUserSetSessionOption() with the TM_SMTP_OPT_TLSMODE option name as shown in the following sample code. Note that this option must be set before you call tfSmtpUserConnect().

    errorCode = tfSmtpUserSetSessionOption(
                            mySmtpClientHandle,
                            TM_SMTP_OPT_TLSMODE,
                            TM_SMTP_TLSMODE_ALWAYS );

If you are running the Treck SMTP client in blocking mode, success or failure of the SSL/TLS negotiations will be reported in the value returned from tfSmtpUserConnect(): TM_ENOERROR or TM_ESERVERPERM respectively. Note that if TM_SMTP_TLSMODE_OPTIONAL mode is used and the server does not support SMTP over SSL, the Treck SMTP client will not attempt to initiate SSL/TLS and no SSL/TLS failure will be reported.

If you are running the Treck SMTP client in non-blocking mode, success or failure of the SSL/TLS negotiations may be reported in the value returned from tfSmtpUserExecute(). If you provided a callback function pointer, your callback function will be called with event TM_SMTP_EVENT_SECURE_OK or TM_SMTP_EVENT_SECURE_FAIL at the end of the SSL/TLS negotiations (if SSL/TLS was negotiated at all).

Function References

Blocking Mode Example

/* SMTP Client example: Send an email with a blocking mode SMTP session */
 
include <trsocket.h>
 
int tfxSmtpSendSimple(
    ttSmtpClientHandle        smtpClientHandle,
    struct sockaddr_storage * sockAddrPtr;
 
    char*               senderNamePtr,
    char*               senderAddrPtr,
    char*               replyToNamePtr,
    char*               replyToAddrPtr,
    char*               toNamePtr,
    char*               toAddrPtr,
    char*               ccNamePtr,
    char*               ccAddrPtr,
    char*               bccNamePtr,
    char*               bccAddrPtr,
    char*               subjectPtr,
    char*               dateTimePtr,
    char*               bodyPtr,
    int                 bodyLen)
{
    int errorCode;
    int sentLen;
 
    sentLen = 0;
 
/* Try to connect to the server */
    errorCode = tfSmtpUserConnect(
                    smtpClientHandle,
                    sockAddrPtr,
                    (char *)0,
                    (char *)0);
 
/* Try to send the sender information */
    if (errorCode == TM_ENOERROR)
    {
        errorCode = tfSmtpUserSendSenderInfo(
                    smtpClientHandle, 
                    senderNamePtr,
                    senderAddrPtr,
                    replyToNamePtr,
                    replyToAddrPtr);
    }
 
/* Try to send the receiver direct receiver */
    if (errorCode == TM_ENOERROR)
    {
        errorCode = tfSmtpUserSendRecipientInfo(
                        smtpClientHandle, 
                        toNamePtr, 
                        toAddrPtr, 
                        TM_SMTP_RECIPIENT_TYPE_TO);
    }
 
/* Try to send the recipient as copied receiver */
    if (errorCode == TM_ENOERROR && ccAddrPtr != (char*)0)
    {
        errorCode = tfSmtpUserSendRecipientInfo(
                        smtpClientHandle, 
                        ccNamePtr, 
                        ccAddrPtr, 
                        TM_SMTP_RECIPIENT_TYPE_CC);
    }
 
/* Try to send the recipient as blind copied receiver */
    if (errorCode == TM_ENOERROR && bccAddrPtr != (char*)0)
    {
        errorCode = tfSmtpUserSendRecipientInfo(
                        smtpClientHandle, 
                        bccNamePtr, 
                        bccAddrPtr, 
                        TM_SMTP_RECIPIENT_TYPE_BCC);
    }
 
/* Send the beginning of the mail message */
    if (errorCode == TM_ENOERROR)
    {
        errorCode = tfSmtpUserSendBom(
                        smtpClientHandle,
                        subjectPtr,
                        dateTimePtr,
                        0);
    }
 
/* Send the whole body */
    while ( (errorCode == TM_ENOERROR) && (bodyLen > 0) )
    {
        sentLen = tfSmtpUserSendBody(
                smtpClientHandle,
                bodyPtr,
                bodyLen,
                &errorCode);
        if (sentLen > 0)
        {
            bodyLen -= sentLen;
            bodyPtr += sentLen;
        }
    }
 
/* Send the end of mail message */
    if (errorCode == TM_ENOERROR)
    {
        errorCode = tfSmtpUserSendEom(smtpClientHandle);
    }
 
/* Disconnect with the server */
    if (errorCode == TM_ENOERROR)
    {
        errorCode = tfSmtpUserDisconnect(smtpClientHandle);
    }
 
    return errorCode;
}

Non-blocking Mode Example

Shared Variables

int     tvSendMailInProgress;
int     tvToSent;
int     tvCcSent;
int     tvBccSent;
int     tvBomSent;
char *  tvSenderNamePtr;
char *  tvSenderAddrPtr;
char *  tvReplyNamePtr;
char *  tvReplyAddrPtr;
char *  tvToNamePtr;
char *  tvToAddrPtr;
char *  tvCcNamePtr;
char *  tvCcAddrPtr;
char *  tvBccNamePtr;
char *  tvBccAddrPtr;
char *  tvSubjectPtr;
char *  tvDateTimePtr;
char *  tvBodyPtr;
int     tvBodyLen;
int     tvErrorCode;

Callback Function

void mySmtpCb(ttSmtpClientHandle smtpClientHandle, int event)
{
    int         sentLen;
 
    tvErrorCode = TM_ENOERROR;
 
    switch(event)
    {
/* We just got connected, so let's send the sender information */
    case TM_SMTP_EVENT_CONNECTED:
        tvErrorCode = tfSmtpUserSendSenderInfo(
                smtpClientHandle,
                tvSenderNamePtr,
                tvSenderAddrPtr,
                tvReplyNamePtr,
                tvReplyAddrPtr);
 
        break;
 
    case TM_SMTP_EVENT_DISCONNECTED:
        tvSendMailInProgress = 0;
        break;
 
    case TM_SMTP_EVENT_RESET:
        tvSendMailInProgress = 0;
 
/* Sender information is acknowledged, so let's send the recipient information */
    case TM_SMTP_EVENT_SENDER_ACK:
        tvErrorCode = tfSmtpUserSendRecipientInfo(
                                smtpClientHandle,
                                tvToNamePtr,
                                tvToAddrPtr,
                                TM_SMTP_RECIPIENT_TYPE_TO);
        tvToSent = 1;
        break;
 
    case TM_SMTP_EVENT_RECIPIENT_ACK:
        if (!tvCcSent)
        {
/* "TO" recipient acknowledged, so let's send the "CC" recipient information */
            tvErrorCode = tfSmtpUserSendRecipientInfo(
                                smtpClientHandle,
                                tvCcNamePtr,
                                tvCcAddrPtr,
                                TM_SMTP_RECIPIENT_TYPE_CC);
            tvCcSent = 1;
            break;
        }
 
        if (!tvBccSent)
        {
/* "CC" recipient acknowledged, so let's send the "BCC" recipient information */
            tvErrorCode = tfSmtpUserSendRecipientInfo(
                                smtpClientHandle,
                                tvBccNamePtr,
                                tvBccAddrPtr,
                                TM_SMTP_RECIPIENT_TYPE_BCC);
            tvBccSent = 1;
            break;
        }
 
/* All recipients acknowledged, so let's send the Begin of Mail message */
        if (!tvBomSent)
        {
            tvErrorCode = tfSmtpUserSendBom(
                                smtpClientHandle,
                                tvSubjectPtr,
                                tvDateTimePtr,
                                0);
            tvBomSent = 1;
            break;
        }
        break;
 
    case TM_SMTP_EVENT_BOM_ACK:
 
/* Begin of Mail message acknowledged, so let's send the mail body */
 
/* Let's send a short string as part of the body, we don't mind if it doesn't
 * go through, so we're not checking the return value nor the actual length
 * of the message sent
 */
        sentLen = tfSmtpUserSendBody(
                smtpClientHandle,
                "Hello\n\n",
                strlen("Hello\n\n"),
                &tvErrorCode);
 
        if(   (tvErrorCode != TM_ENOERROR)
           && (tvErrorCode != TM_EINPROGRESS) )
        {
            break;
        }
 
 
/* We insist on sending the whole body unless an error occurs */
        while(tvBodyLen > 0 )
        {
            sentLen = tfSmtpUserSendBody(
                    smtpClientHandle,
                    tvBodyPtr,
                    tvBodyLen,
                    &tvErrorCode);
            if(   (tvErrorCode != TM_ENOERROR)
               && (tvErrorCode != TM_EINPROGRESS) )
            {
                break;
            }
 
            tvBodyLen -= sentLen;
            tvBodyPtr += sentLen;
        }
 
/* Let's send a short string as part of the body, we don't mind if it doesn't
 * go through, so we're not checking the return value nor the actual length
 * of the message sent
 */
        sentLen = tfSmtpUserSendBody(
                smtpClientHandle,
                "Bye-bye\n",
                strlen("Bye-bye\n"),
                &tvErrorCode);
 
        if(   (tvErrorCode != TM_ENOERROR)
           && (tvErrorCode != TM_EINPROGRESS) )
        {
            break;
        }
 
/* Every part of the body is sent, Let's tell the server that this is the end
 * of the mail
*/
        tfSmtpUserSendEom(smtpClientHandle);
 
        break;
 
    case TM_SMTP_EVENT_EOM_ACK:
/* We've successfully sent the mail, let's reset the in progress indicator,
 * to notify the foreground code that the process has stopped
 */
        tvSendMailInProgress = 0;
        break;
 
    case TM_SMTP_EVENT_SERVER_ERROR:
/* Failed to send the mail */
        tvErrorCode = TM_ESERVERPERM;
        break;
 
    case TM_SMTP_EVENT_SERVER_RETRY:
/* Server asks us to retry*/
        tvErrorCode = TM_ESERVERTEMP;
        break;
 
    case TM_SMTP_EVENT_TIMEOUT:
/* the process has timed out */
        tvErrorCode = TM_ETIMEDOUT;
        break;
 
    default:
        break;
 
    }
 
    if ((tvErrorCode != TM_ENOERROR) && (tvErrorCode != TM_EINPROGRESS) )
    {
/* An error has happened, let's reset the in progress indicator,
 * to notify the foreground code that the process has stopped
 */
        tvSendMailInProgress = 0;
    }
}


Foreground Code

#include <trsocket.h>
.
.
.
struct sockaddr_storage sockAddr;
ttSmtpClientHandle      smtpClientHandle;
int                     errorCode;
 
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.ss_len = sizeof(sockAddr);
sockAddr.ss_family = AF_INET;
sockAddr.ss_port = htons(m_nPort);
inet_pton(AF_INET, "192.168.0.1", &sockAddr.addr.ipv4.sin_addr);
 
smtpClientHandle = tfSmtpUserNewSession(
                        "Treck Adv Mailer",
                        mySmtpCb,
                        5,  /* timeout seconds */
                        TM_BLOCKING_OFF,
                        & errorCode);
 
if(errorCode != TM_ENOERROR)
{
    MessageBox("Can't create new SMTP session", NULL, MB_OK);
    return;
}
 
/* Set the variables shared with the call back function */
 
tvSendMailInProgress = 1;
tvToSent        = 0;
tvCcSent        = 0;
tvBccSent       = 0;
tvBomSent       = 0;
tvSenderNamePtr = (char*) 0;
tvSenderAddrPtr = "me@treck.com";
tvReplyNamePtr  = (char*) 0;
tvReplyAddrPtr  = "me@treck.com";
tvToNamePtr     = (char*) 0;
tvToAddrPtr     = "me@treck.com";
tvCcNamePtr     = (char*) 0;
tvCcAddrPtr     = "me@treck.com";
tvBccNamePtr    = (char*) 0;
tvBccAddrPtr    = "me@treck.com";
tvSubjectPtr    = "SMTP Client example";
tvDateTimePtr   = "Sep 8, 2003: 2:43 PM";
tvBodyPtr       = "This is an SMTP Client example";
tvBodyLen       = strlen(tvBodyPtr);
tvErrorCode     = TM_ENOERROR;
 
/* Connect, the rest will be taken care by the call back function */
errorCode = tfSmtpUserConnect(
                        smtpClientHandle,
                        &sockAddr,
                        (char *)0,
                        (char *)0 );
if (    errorCode != TM_ENOERROR
     && errorCode != TM_EINPROGRESS)
{
printf("Failed to connect to the mail server -- %s\n", tfStrError(errorCode));
    return;
}
 
 
while(tvSendMailInProgress)
{
/* execute timer */
/* check interface */
tfSmtpUserExecute(smtpClientHandle)
}
 
printf("Result: -- %s", tfStrError(tvErrorCode) );
 
tfSmtpUserFreeSession(smtpClientHandle);


Table of Contents >> Optional Protocols