Forum OpenACS Q&A: Instructions on how to send emails from your package

How to send emails from your package

This is a code review. So please test and verify. Thanks for any contrib. Maybe we can define a general API for emails in future.

  1. ns_sendmail
  2. private, calling sendmail directly
    • ns_sendmail
      ns_sendmail to from subject body ?extraheaders? ?bcc?

  3. acs-mail-lite Package
    • acs_mail_lite::deliver_mail, main proc, using ns_sendmail, or smtp or sendmail internally
      acs_mail_lite::deliver_mail -to_addr $to -from_addr $from -subject $sub -body $body -package_id $id [-extraheaders -bcc -valid_email_p 0]
    • acs_mail_lite::sendmail
      acs_mail_lite::sendmail -from_addr $from -sendlist $mylist -msg $msg -package_id $id -message_id $m_id [-valid_email_p 0]
    • acs_mail_lite::smtp (private using _ns_smtp_send/recv
      acs_mail_lite::smtp -from_addr $from -sendlist $my_list -msg $msg -message_id $m_id -package_id $id [-valid_email_p 0]
      
    • acs_mail_lite::send
      acs_mail_lite::send -send_immediately $flag -valid_email $flag1 -to_addr $to -from_addr $from -body $body [-subject -extraheaders -bcc -package_id]
    • acs_mail_lite::send_immediately
      acs_mail_lite::send_immediately -to_addr $to -from_addr $from -body $body -package_id $id [-subject -extraheaders -bcc -valid_email_p 0]
      

  4. acs-mail Package

  5. Which from my understanding works with the content repository
    • Simple Email
              set request_id [db_exec_plsql notify "
                begin
                :1 := acs_mail_nt.post_request(
                    party_from   => :user_id,
                    party_to     => :party_id,
                    expand_group => 'f',
                    subject      => :subject,
                    message      => :message
                );
                end;
              "]
      
    • HTML Email
      set header_subject "My subject"
      set content "My message is here."
      set content_type "text/plain"
      
      # create a mail body
      set body_id [acs_mail_body_new -header_subject $header_subject -content $content -content_type $content_type]
      
      # queue it
      set sql_string "select acs_mail_queue_message.new (
                        null,             -- p_mail_link_id
                        :body_id,         -- p_body_id
                        null,             -- p_context_id
                        now(),            -- p_creation_date
                        :user_id,         -- p_creation_user
                        :ip_addr,         -- p_creation_ip
                        'acs_mail_link'   -- p_object_type
                      );"
      
      set mail_link_id [db_string queue_message $sql_string]
      
      # put in in outgoing queue
      set sql_string "insert into acs_mail_queue_outgoing ( message_id, envelope_from, envelope_to ) values ( :mail_link_id, :from_addr, :to_addr )"
      
      db_dml outgoing_queue $sql_string
      
    • Multipart Email
      # create the multipart message ('multipart/mixed')
      set multipart_id [acs_mail_multipart_new -multipart_kind "mixed"]
      
      # create an acs_mail_body (with content_item_id = multipart_id )
      set body_id [acs_mail_body_new -header_subject "My subject" -content_item_id $multipart_id ]
      
      # create a new text/plain item
      set content_item_id [db_string create_text_item "
          select content_item__new (
              'acs-mail message $body_id-1', -- name
              ...
              'text message',                -- title
              ...
              'text/plain',                  -- mime_type
              ...
              'plain message content'        -- text
              ...
          )
      "]
      
      acs_mail_multipart_add_content -multipart_id $multipart_id -content_item_id $content_item_id
      
      # create a new text/html item
      
      set content_item_id [db_string create_html_item "
          select content_item__new (
              'acs-mail message $body_id-2', -- name
              ...
              'html message',                -- title
              ...
              'text/html',                   -- mime_type
              ...
              'HTML message content', -- text
              ...
      
          )
      "]
      
      acs_mail_multipart_add_content -multipart_id $multipart_id -content_item_id $content_item_id
      
      # create a new binary item
      # from file that was uploaded
      
      set mime_type "image/jpeg"
      
      set content_item_id [db_string create_jpeg_item "
          select content_item__new (
              'acs-mail message $body_id-3', -- name
              ...
              :mime_type,                    -- mime_type
              ...
          )
      "]
      
      set revision_id [db_string create_jpeg_revision "
          select content_revision__new ( ... )
      "]
      
      db_transaction {
      	db_dml content_add "update cr_revisions set content = empty_blob() where revision_id = :revision_id returning content into :1" -blob_files [list ${content_file.tmpfile}]
      }
      
      
      db_1row make_live {
      	select content_item__set_live_revision(:revision_id);
      }
      
      # get the sequence number, so we can make this part an attachment
      set sequence_num [acs_mail_multipart_add_content -multipart_id $multipart_id -content_item_id $content_item_id]
      
      db_dml update_multiparts "update acs_mail_multipart_parts set mime_disposition='attachment; filename=\"myfile.jpg\"' where sequence_number=:sequence_num and multipart_id=:multipart_id"
      
      set sql_string "select acs_mail_queue_message.new (
                        null,             -- p_mail_link_id
                        :body_id,         -- p_body_id
                        null,             -- p_context_id
                        now(),            -- p_creation_date
                        :user_id,         -- p_creation_user
                        :ip_addr,         -- p_creation_ip
                        'acs_mail_link'   -- p_object_type
                      );"
      
      set mail_link_id [db_string queue_message $sql_string]
      
      set sql_string "insert into acs_mail_queue_outgoing ( message_id, envelope_from, envelope_to ) values ( :mail_link_id, :from_addr, :to_addr )"
      
      db_dml outgoing_queue $sql_string
      
      The tcl procedure that sends out the mail (acs_mail_process_queue) calls a procedure acs_mail_body_to_output_format that sets up the parameters to call ns_sendmail. This proc gets the from and to address from acs_mail_bodies. acs_mail_process_queue then overwrites the from and to address with the values from envelope_from and envelope_to in the acs_mail_queue_outgoing table. So, if you supply the from and to address in acs_mail_bodies, they get overwritten by whatever is in acs_mail_queue_outgoing. (Actually it's a little more complicated than that because acs_mail_body_to_output_format also adds the addresses as Headers in the 5th param to ns_sendmail, so you could get duplicate To: and From: headers. Bottom line: Don't fill in the To: and From: fields in acs_mail_bodies. Instead, create a unaddressed mail body and then provide the To: and From: address in acs_mail_queue_outgoing. Garbage collection is mentioned a lot, but not implemented.

  6. bulk-mail package
    • bulk_mail::new
      bulk_mail::new -from_addr $from -message $msg -query $query [-package_id -send_date -date_format -subject -reply_to -extra_headers -message_type]
      

    The actual proc that sends all the messages in the queue is bulk_mail::sweep which itself uses acs_mail_lite::send. Supported message types are "html" and "text".

    query that must select the email address to send to as 'email' and can select any other values that will be interpolated into the subject and message of the bulk_mail for each recipient. if column 'foo' is selected it's value will be interpolated anywhere that '{foo}' appears in the subject or message of the bulk_mail. if columns 'from_addr', 'reply_to', 'subject', or 'message' are selected, their respective values will also become the 'from_addr', 'reply_to', 'subject', and 'message' on a per recipient basis.

  7. Mailing list package
    • mail_job::send
      mail_job::send -mail_job_id $id

      This proc uses acs_mail_lite::send internally and supports "multipart/alternative", "text/html" and "text/plain". It is database driven.

    • mail_job::send_mail
      mail_job::send_mail -to_addr $to -from_addr $from -subject $subj -body $body -mime_type $mime -charset $charset [-package_id]
      

      This proc uses acs_mail_lite::send internally and supports "text/html" and "text/plain" Light-weight version, no database.

  8. notification package
    • notification::email::send
      notification::email::send $from_user_id $to_user_id $reply_object_id $notification_type_id $subject $content_text $content_html

      As you can see only emails from known users to OpenACS can benefit from this proc. To use the proc you need to enable your package to support notification. Internally acs_mail_lite::send is used.

  9. References
    1. /packages/acs-mail/www/doc/openacs-mail.html
    2. https://openacs.org/doc/current/tutorial-notifications.html
Collapse
Posted by Nima Mazloumi on

A good link on emails (in german thou) can be found here

Also here an example of a typical email:

Return-Path: <mazloumi@uni-mannheim.de>
X-Flags: 0000
Delivered-To: GMX delivery to nima.mazloumi@gmx.de
Received: (qmail 30652 invoked by uid 65534); 18 Aug 2004 12:31:30 -0000
Received: from rumms.uni-mannheim.de (EHLO rumms.uni-mannheim.de) (134.155.50.52)
  by mx0.gmx.net (mx060) with SMTP; 18 Aug 2004 14:31:30 +0200
Received: from [134.155.48.129] (flores.wifo.uni-mannheim.de [134.155.48.129])
	(authenticated bits=0)
	by rumms.uni-mannheim.de (8.12.11/8.12.11) with ESMTP id i7ICVSUm000051;
	Wed, 18 Aug 2004 14:31:28 +0200 (MEST)
Message-ID: <41234C21.40908@uni-mannheim.de>
Date: Wed, 18 Aug 2004 14:31:29 +0200
From: Nima Mazloumi <mazloumi@uni-mannheim.de>
User-Agent: Mozilla Thunderbird 0.6 (Windows/20040502)
X-Accept-Language: en-us, en
MIME-Version: 1.0
To: "nima.mazloumi@gmx.de" <nima.mazloumi@gmx.de>
CC: test@test.de
Subject: A dummy email with 2 attachments
Content-Type: multipart/mixed;
 boundary="------------040109040901060007040900"
X-Virus-Scanned: by amavisd-new
X-GMX-Antivirus: -1 (not scanned, may not use virus scanner)
X-GMX-Antispam: 0 (Mail was not recognized as spam)

This is a multi-part message in MIME format.
--------------040109040901060007040900
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

An email with 2 attachments:
- an image
- a simple text file


--------------040109040901060007040900
Content-Type: image/gif;
 name="logo-user.gif"
Content-Transfer-Encoding: base64
Content-Disposition: inline;
 filename="logo-user.gif"

R0lGODlhbAAgAPcAABwaFIyOjFRWVMzKxDw6POTm5HRybKyurCwqJNza1JyenGRmZExKRPT2
9ISChLy+vCQiHJSWlNTSzDQyLFxeXERCPOzu7Hx6fLS2tOTi5GxubFRSVPz+/IyKhBwaHJSS
lMzKzOzq7HRydCwqLNza3KyqrGxqbExKTPz6/MTGxCQiJJyanNTS1DQyNGRiZERCRJSSjFxa
XDw+POzq5LSytKSipGxqZPz69ISGhMTCxJyalGRiXPTy9Hx+fLy6vIyKjBweHMzOzHR2dCwu
LNze3ExOTCQmJNTW1DQ2NERGRBJWAAAAAIKEALIAAEwAAAAAAKkAAP8AJ/8ATv8AAAAymAAA
7QAQEgAAAO4ESCIA40oAeAAAANzkAPEEABIAAAAAAFe3MQBcMADRMAB3IHwAJe0AIBIAAAAA
d1ABSO0A4xIAeAAAAAABDAAAAAAAAAAAAG9sABBvAOVnAHdvAEstbJx1WwBz5QBld/9yAP8A
AP8AAP8AAACAGADsAgASFQAAAACJGAAcAgAPFQBbAAANAACmAAAQFQBbAGQAAAAAAAAAFQAA
ACAAAAMAAAAAAAAAAAYyB/YAAOQQAHcAAA8AeB4A7eUAEncAAAATAAFdAADRAAB3AFh0AGvs
AAESAAAAAADIAAAoABTlAAB3AKQAAOoAABIAAAAAAAAAQgAA/gAA5AAAdwBYV/trABIUAAAA
APAA/4gB//kA/3cA/4gEVxwpAPTlAHd3AP+U3P/s8f8SEv8AADdkAJAvAPTmAHd3AOAAnBgA
7eUAEncAAAAAGAAA+BQASQAAAABvfAAQ7QDlEgB3AFhLg2ucshQATAAAAACYsAHt7QASEgAA
AGSYby8QEObl5Xd3dwA4RwAO7wBNEgAAAAE8pwAO/wBN/wAAfxoA8AAB7gAAEgAAAACUAADs
AAASAAAAAAAAbwABEAAA5QAAdxXsAADBAABMAAAAABUAGAAAAgAQFQAAAA1M8AD24gASRQAA
AA0w8ABC7gBEEgAAAIkE8BD27hASEo4AACH5BAAAAAAALAAAAABsACAABwj/ADkIHEhQIIgR
I4yMQGAEgQoQBSPGYGhEoUIVQ4xAWIhgyBARKQaiQEEwQEKKChFo4EBSpEsOESomHIFRBYcG
LoyoaDHkB0IhKEQojBgRBMOEKY0MIEqQwkWGCDxMAIIQwhAgKhBAQOCCCI+XKyw2rAiEgAKW
LVkW/CB2pocGN1G0iAFXIZIGKpAYYUrw4MyKFUPy5RBjLGAERxFXHOLQCBAZCdKS1KEC6VEI
QCpATPvyw9+EldVyMEKBpREDKjYYETJisMGZRytCHOwU6kwVZDPqhNCCatYGnCNUBtxwSNYN
BSKmZfu3RUK0KIy4IDniw4vUAfa6Ngp77NLBhWkC/wbQ4vLJERCMTEAwAsfalLhxL1QBBCjJ
r+8VGreYVkUMgUZ8gMMIJQjnGgcHJYWUYHzV1tBfOukFxH5XjTDhEHC1xBZgCxFn3AcCtSSi
Z7ABMZRaLuAwUgwlFEABDzT8tx1SlhkxG1+FUUQRBTdY8EAPMgDRkQpTVYTAjRyQaGSHt7WQ
gogExXQRQhURNFKIVh6IYHcLuuYUjRXtINANHISQxElE6jREBCSRtCFFuDWEWwsQEJABUcwp
RBVNaGEJpZavQcgQkkTlaGRFpV15QwlaZaUjiC3FhNAQ6bFnlUwIFJHhTTetQCVNoAKqpVFt
VfRdg3JaNMIGIqJAQlagXf8UAEEbHobbVR4BYMQQBviJggImQrgXZ6IW9alhDDIVnmEjuMAB
fhykoFNSDQWwnE7ooaTYCBllVIJabg7XVrHbJZYUoRHVhttRAojWQAQWPqgRgcvNtNGEKmyk
0VVZyRBEnz4NIZ54opFrrLBGJFsosxVN15IPNKYn3hG0sjcWRhDEIKSF5TlWRAhq1XpbawYz
lWCH7KlwKlNfHjqCDBFE4MAJEyxZUXobwDXQB/FZSCkCLFRQE0NSqSCCzivIJ9awJRsr06Ho
FqSuxdhyq0Jl+3XkwQsYvHeSVR4MwUEJQwBAaYlsJnmRTKE1bSx7sSXs5aEcxkmlTOmpsNKV
AsH/KydSRpBkEpP62siBSQ4BzrSW+EHZEghTPr3ywmAaqW/iFY5wAZ7CPicQBRYyyRASRPhd
N8nF/jkQ5AhBFdjcOipklWUM7bTBAS8JlCdK2qHAwwvGIdRzET1sJWzBB3I2UpsgtPB0h1ET
NJFhli620dUtYNBmm7QKyxBBLHi0EMqPVQYm6uTywL1AKWSkuNy0larCmQ+mN4EKAuiMZ6lV
ilYCqBxqgdnSs7bFJU95fMuB+RLzkLkdpiECCMCELFIZFfTAT7SiFmAicgFs3exBwZqSqP4k
GQ6wYFqKm1y6GDYCAVigCBC41K4s9C2i+A1w3xMJChqgmjjlq0aAK1YB/36gFyO8gAYWCFEQ
LGIuha1QcRBw1gOM0AIPWAwjBGDB/lC2QSzdJANFRMxVVKUq5PHFAqppwQIdgB/WWYwi0RuI
Ux44AhmJYIKty9cJQMYZtlCtiy5BgQLVRJWnEUdUOJiXh0KCAhYIT1W2cV9xbMIBAVysQzLi
ARLihCua2EAtX0FBWE7SGAzlrgGjFGNHEGM+mxCLKUVMFU2E8BXI7YqOD8xX/0CnEbipYAFq
OQDWJmQixNQAXEkaTut0kiVQLgAroJHY3fjmGvkITIbt4sADBBYn4hgyZQjgwA0mIjDA+AdL
JtiYbXCTA9F4Rk4QyMpeyCSalpipIuuBTei8OP8YSsXTIWeLAUlYd55D1c5SGxGIxrYCmBZs
AEsk2GRWjLORIbRgBgOJwIQSQqmMAIefA0lBNCsUn94dSAa3BMxVPgAcFojLm4C5F7eM0IBx
Uk03zopSqhhDQBN8hQcfMA6c+jcYHSwmI4lToxmZUoMx7gqgWjRhZaiCGI4sRDdWAQIQPger
QgIhp2kRgEMcslELRUAgJonpfpAJHZKQqQfxUojwAFAsDVDqZiPAnUAeoMb3mVNeEEACD25g
A8foqFnQquX9GEKnhPCEBhyowdVkp5EWJBGkamlAAxiAEUMS4LKASoEJdhCDCySHU8WCC36g
lSXurU+cA4GW/rAElxsNvFIgcNEZPVsSSi0FBAA7
--------------040109040901060007040900
Content-Type: text/plain;
 name="test.txt"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="test.txt"

some text as attachement
--------------040109040901060007040900--

Collapse
Posted by Jade Rubick on
Nima, great overview. Feel free to add to the docs.
Collapse
Posted by Nima Mazloumi on

I still need to verify everthing. You example in the docs works very well.

Now I need a working example for attachments. acs-mail breaks in postgres. Brian conformed this to work on Oracle thou:

set generated_name "test"
set csv_string "contents of csv file"
set csv_file_name "outputfilename.csv"

set file_handle [open $csv_file_name w]
puts $file_handle $csv_string
close $file_handle

set ip_addr [ad_conn peeraddr]
set modifying_user [ad_conn user_id]

set from_addr [db_string some_sql "select email from parties where 
party_id = :modifying_user"]

# create the multipart message ('multipart/mixed')
set multipart_id [acs_mail_multipart_new -multipart_kind "mixed"]

# create an acs_mail_body (with content_item_id = multipart_id )
set body_id [acs_mail_body_new -header_subject $generated_name 
-content_item_id $multipart_id ]

# create a new text/plain item
set content_item_id [db_exec_plsql create_text_item {
    begin
        :1 := content_item.new (
               name      => :generated_name, 
               title => :generated_name,
               mime_type => 'text/plain',
               text => :csv_string);
    end;}]    

set sequence_num [acs_mail_multipart_add_content -multipart_id 
$multipart_id -content_item_id $content_item_id]

db_dml update_multiparts "
      update acs_mail_multipart_parts 
      set mime_disposition='attachment; filename=\"$generated_name\"' 
      where sequence_number=:sequence_num 
      and multipart_id=:multipart_id"

# queue it
set mail_link_id [db_exec_plsql queue_the_mail {
    begin

        :1 := acs_mail_queue_message.new (
                  null,             -- p_mail_link_id
                  :body_id,         -- p_body_id
                  null,             -- p_context_id
                  sysdate,          -- p_creation_date
                  :modifying_user,  -- p_creation_user
                  :ip_addr,         -- p_creation_ip
                  'acs_mail_link'   -- p_object_type
                  );
    end;
    }
    ]      

# send a copy of the mail to the logged in user
set sql_string "
    insert into acs_mail_queue_outgoing
     ( message_id, envelope_from, envelope_to )
    values
     ( :mail_link_id, :from_addr, :to_addr )"

set to_addr $from_addr
if {![empty_string_p $to_addr]} {
  db_dml outgoing_queue $sql_string
} else {
  ns_log Error "To email address  is blank"
}

In postgres when I call

set body_id [acs_mail_body_new -header_subject $generated_name 
-content_item_id $multipart_id ]
I get the following error
ERROR:  Function acs_mail_body__new("unknown", "unknown", "unknown", "unknown",
"unknown", "unknown", "unknown", "unknown", "unknown", "unknown", "unknown",
timestamptz, "unknown", "unknown", "unknown") does not exist

	Unable to identify a function that satisfies the given argument types

	You may need to add explicit typecasts



SQL: 



select acs_mail_body__new (

  NULL,				-- body_id 

  NULL,		-- body_reply_to 

  NULL,			-- body_from 

  NULL,			-- body_date 

  NULL,	-- header_message_id 

  NULL,		-- header_reply_to 

  'test',      -- header_subject 

  NULL,			-- header_from 

  NULL,			-- header_to 

  '2015',		-- content_item_id 

  'acs_mail_body',		-- object_type

  now(),				-- creation_date

  NULL,		-- creation_user 

  NULL,			-- creation_ip 

  null					-- context_id

);
The function doesn't exists. So I assume that noone is really maintaining or using acs-mail.
Collapse
Posted by Randy O'Meara on
Nima,

Try adding the following just above the definition of acs_mail_body__new in file acs-mail/sql/postgresql/acs-mail-packages-create.sql

select define_function_args('acs_mail_body__new','body_id,body_reply_to,body_from,body_date,header_message_id,header_reply_to,header_subject,header_from,header_to,content_item_id,object_type,creation_date,creation_user,creation_ip,context_id');

I'm using acs-mail to send multipart emails. I've gotten it to work with PG (the above mention on my older 5x installation needed "date" type replaced with timestamptz for parameter types in the actual function definition).

However the proc acs_mail_base64_encode is using homegrown uuencoding, which works fine for word files, but it corrupts zip and jpegs for me. Doh! After some investigating, ns_uuencode doesn't return the expected result on my system either.

http://www.panoptic.com/wiki/aolserver/923 mentions the base64 package for tcllib. Is this the best bet to fix this problem? Anyone have experience with dealing with uuencoding under AOLserver that has some advice?

Thanks,

The proc in question where the corruption happens is acs_mail_base64_encode in the "ns_uuencode doesn't work" codeblock.

Here's the switch that I tracked things back to:

 switch $state {
            0 { # OK }
            1 { append result $base64_en([expr {(($old << 4) & 0x30)}])== }
            2 { append result $base64_en([expr {(($old << 2) & 0x3C)}])=  }
        }

With jpegs, the encoded string ends with "==", which translates to state being 1 and zips end with state being 2. Word docs on the other hand end with a state of 0 and they are fine.

Any hints greatly appreciated.

I've got attachment encoding working (i.e. not corrupting the image or zip) by hacking together a solution using the mpack command.

All of the various Tcl flavors or base64::encode or ns_uuencode I was able to setup (including adding the tclTrf package to AOLserver) resulted in the same sort of corruption, so that's why I went with mpack. It just worked by creating a legit MIME which I then pulled apart and only grabbed what I wanted.

After things calm down on my current project and I have a chance to setup a dev environment for OpenACS HEAD, I'll try to post the details of how I got it working with PG here. Email me if you need advice before then.

Before anyone goes down that road:

Do not use ns_sendmail. acs-mail-lite is the central entity for mail sendin in OpenACS 5.2 and 5.3.

If you have tcllib installed it correctly works with the mime:: procs and allows sending of multipart e-mails with attachments (the procedure is called acs_mail_lite::complex_send).

Furthermore it has a switch to turn of mail sending alltogether (useful for your staging server) and allows the mail-tracking package to track all outgoing and (if you configured it correctly) incoming e-mail.