Forum OpenACS Q&A: Re: ns_sendmail replacement?

Posted by Andrew Piskorski on
This is quite the long thread, but I think no one has yet added generation of the Message-Id in any of the versions of ns_sendmail above? Hm, at first glance, looks like the AOLserver 4.0 beta sendmail.tcl has some slight improvements over 3.3, but still doesn't do that. Well, I hacked my ns_sendmail to do some simply Message-Id generation a year or two ago to do that. It wasn't hard, but FYI, here's the code:

My _ns_smtp_recv, _ns_smtp_send, and ns_sendmail procs are unchanged from the versions shipped with 3.3+ad13. I just added this extra top level nsv command, and changed _ns_sendmail a bit. Note there are only a few lines of changed code here! (But I find trying to read diffs in BBoard annoying, so I didn't do that.) After the

if { ! $message_id_already_done_p }
line it's all stock, except for some comments:

if { ![nsv_exists ns_sendmail sequence] } {
   nsv_set ns_sendmail sequence 0

proc _ns_sendmail {smtp smtpport timeout tolist bcclist \
        from subject body extraheaders} {
    ## Put the tolist in the headers
    set rfcto [join $tolist ", "]
    ## Build headers
    set msg "To: $rfcto\nFrom: $from\nSubject: $subject\nDate: [ns_httptime [ns_time]]"
    ## Insert extra headers, if any (not for BCC)
    set message_id_already_done_p 0
    if ![string match "" $extraheaders] {
        set size [ns_set size $extraheaders]
        for {set i 0} {$i < $size} {incr i} {
           set key [ns_set key $extraheaders $i]
           # This is rather hack-ish...
           if { [string compare $key {Message-ID}] == 0 } {
              set message_id_already_done_p 1
           append msg "\n${key}: [ns_set value $extraheaders $i]"

    # Insert a unique "Message-ID:" header, but only if the caller did
    # not manually include a Message-ID header:
    # An application could use the Message-ID header for
    # e.g. threading support, but we're not trying to do anything
    # fancy like that here.  We just want to include a globally-unique
    # ID.  Why?  Well, for one thing, since most email user agents
    # include a Message-ID, but most SPAM software does not, some
    # anti-SPAM software filters out email which does not have a
    # Message-ID...
    # Note: The $message_id below is guaranteed to be globally unique
    # if and only if *ALL* of the following conditions are true:
    # 1. Your unix box's hostname (which is what [ns_info hostname]
    #    returns) is set to a fully-qualified name like
    #    "", NOT just a local hostname like
    #    "philip".
    # 2. Your fully-qualified hostname is in fact globally-unique.
    #    AKA, you didn't do something foolish like set up two separate
    #    machines that both think their hostname is
    #    "".
    # 3. On your unix host, you have only ONE AOLserver running with
    #    the server name returned by [ns_info server].
    # 4. Since [ns_info boottime] is in seconds, you never restart
    #    your AOLserver multiple times in < 1 second, jump your system
    #    clock backwards in time, or etc.
    # 5. Once the "ns_sendmail sequence" nsv variable is set, you
    #    never manually fool with it to re-set it to a previous value.
    #    While the server is running, this value must always increase,
    #    never decrease.
    #, 2001/10/11 11:51 EDT

    if { ! $message_id_already_done_p } {
       set message_id "[nsv_incr ns_sendmail sequence].[ns_info boottime].[ns_info server]@[ns_info hostname]"
       append msg "\nMessage-ID: $message_id"
    ## Blank line between headers and body
    append msg "\n\n$body\n"
    ## Terminate body with a solitary period
    foreach line [split $msg "\n"] { 
        if [string match . $line] {
            append data .
        append data $line
        append data "\r\n"
    append data .
    ## Open the connection
    set sock [ns_sockopen $smtp $smtpport]
    set rfp [lindex $sock 0]
    set wfp [lindex $sock 1]

    ## Perform the SMTP conversation
    if { [catch {
        _ns_smtp_recv $rfp 220 $timeout
        _ns_smtp_send $wfp "HELO AOLserver [ns_info hostname]" $timeout
        _ns_smtp_recv $rfp 250 $timeout
        _ns_smtp_send $wfp "MAIL FROM:<$from>" $timeout
        _ns_smtp_recv $rfp 250 $timeout

        # TODO: Above, should optionally take a "Return-Path" to use
        # as the envelope sender address (aka, envelope return path)
        # rather than always using $from.  This would allow using
        # VERPs, for instance, as discussed at:
        #   ""
        # See also discussion at:
        #   ""
        #, 2001/10/11 10:25 EDT
        ## Loop through To list via multiple RCPT TO lines
        foreach toto $tolist {
            _ns_smtp_send $wfp "RCPT TO:<$toto>" $timeout
            _ns_smtp_recv $rfp 250 $timeout     
        ## Loop through BCC list via multiple RCPT TO lines
        ## A BCC should never, ever appear in the header.  Ever.  Not even.
        foreach bccto $bcclist {
            _ns_smtp_send $wfp "RCPT TO:<$bccto>" $timeout
            _ns_smtp_recv $rfp 250 $timeout
        _ns_smtp_send $wfp DATA $timeout
        _ns_smtp_recv $rfp 354 $timeout
        _ns_smtp_send $wfp $data $timeout
        _ns_smtp_recv $rfp 250 $timeout
        _ns_smtp_send $wfp QUIT $timeout
        _ns_smtp_recv $rfp 221 $timeout
    } errMsg ] } {
        ## Error, close and report
        close $rfp
        close $wfp
        return -code error $errMsg

    ## Close the connection
    close $rfp
    close $wfp