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.
- ns_sendmailprivate, calling
sendmail
directly- ns_sendmail
ns_sendmail to from subject body ?extraheaders? ?bcc?
- ns_sendmail
- 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]
- acs_mail_lite::deliver_mail, main proc, using ns_sendmail, or smtp or sendmail internally
- acs-mail Package
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 procedureacs_mail_body_to_output_format
that sets up the parameters to callns_sendmail
. This proc gets the from and to address fromacs_mail_bodies
.acs_mail_process_queue
then overwrites the from and to address with the values fromenvelope_from
andenvelope_to
in theacs_mail_queue_outgoing
table. So, if you supply the from and to address inacs_mail_bodies
, they get overwritten by whatever is inacs_mail_queue_outgoing
. (Actually it's a little more complicated than that becauseacs_mail_body_to_output_format
also adds the addresses as Headers in the 5th param tons_sendmail
, so you could get duplicate To: and From: headers. Bottom line: Don't fill in the To: and From: fieldsin acs_mail_bodies
. Instead, create a unaddressed mail body and then provide the To: and From: address inacs_mail_queue_outgoing
. Garbage collection is mentioned a lot, but not implemented.
- Simple Email
- 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 usesacs_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. - bulk_mail::new
- 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.
- mail_job::send
- 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.
- notification::email::send
- References
- /packages/acs-mail/www/doc/openacs-mail.html
- https://openacs.org/doc/current/tutorial-notifications.html
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--
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.
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');
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,
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.
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.
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.