Sat 18 Feb 2006
Emails w/ Attachment From a PS Online Page
Posted by ChiliJoe under PeopleSoft • Tips & Techniques • PeopleCode • PIAThe PeopleCode SendMail() function is a quick and easy way to send emails from your PeopleSoft application. Unlike workflow routing and Notification classes, SendMail() provides the most functionality when it comes to sending emails. Over the newer releases, PeopleSoft has further added new capabilities to this function like overriding the content-type and sender details. However, the feature that I find the least used is the inclusion of file attachments. This is because in PIA, PeopleCode runs either on the Application server or the Batch server. The files that can be attached should be either 1) located on the server, or 2) accessed by the server through a network share. For this reason, most applications that use file attachments are Application Engine programs (running on the Batch server) that attach log files from within the server.
If you’ve used web-based email services like Yahoo, then you’ll find it a common feature to allow attachments to be uploaded by a user from his workstation and send those attachments with the email message. Is this possible in PeopleSoft’s internet architecture? The answer is yes, this article will show you how.
First, let’s assume we have 2 buttons on a page: [Select Attachment] and [Send]. The [Select Attachment] button will upload the file to a server; the [Send] button will trigger the sending of the email with attachment. Because of PIA’s multi-tiered architecture (web server <--> application server <--> dbms server), uploading a file to the application server is a challenge.
The PeopleCode function for uploading a file from a PeopleSoft page is AddAttachment(). The following dialog is displayed when this function is called on an online page:

PeopleSoft also delivered a PeopleCode-defined function that serves as an interface to AddAttachment() that contains additional filename and error checking. The function is named add_attachment and is located on FILE_ATTACH_WRK.ATTACHADD FieldChange. This is what we’re going to use.
Approach 1: File transfer via FTP
If you look at the documentation of AddAttachment() in PeopleBooks — PeopleCode Developer’s Guide > Understanding File Attachments and PeopleCode, you’ll find that there are two types of destination that this function uploads the file to. The first type of destination is FTP. You can use this destination type if your application server already has an FTP service. The FieldChange PeopleCode on [Select Attachment] would look like this:
Declare Function add_attachment PeopleCode FILE_ATTACH_WRK.ATTACHADD FieldChange;
Component string &ATTACHSYSFILENAME, &ATTACHUSERFILE;
Component string &Sysfilename;
Local number &RetCode;
REM Select sysfilename prefix that will serve as uniqueness identifier;
&ATTACHSYSFILENAME = %UserId;
add_attachment("ftp://user:password@appserver.ftp.address/", "", "", 0, False, "", &ATTACHSYSFILENAME, &ATTACHUSERFILE, 2, &RetCode);
In the above code, prior to the call to add_attachment, &ATTACHSYSFILENAME defines a prefix that will be appended to the actual system file created on the destination. It is set to %UserId to avoid filename conflicts with different users that might simultaneously be uploading a file with the same name. After the add_attachment is executed, the value of &ATTACHUSERFILE is set to the source filename (without path) selected by the user; &ATTACHSYSFILENAME would then set to %UserId | &ATTACHUSERFILE.
The FieldChange PeopleCode for the [Send] button, would then call SendMail() and delete the uploaded file:
Component string &ATTACHSYSFILENAME, &ATTACHUSERFILE;
Local File &tmpfile;
Local String &Sysfilename = "/path/to/ftp/destination/" | &ATTACHSYSFILENAME;
SendMail(0, &MAIL_TO, &MAIL_CC, &MAIL_BCC, &MAIL_SUBJECT, &MAIL_TEXT, &Sysfilename, &ATTACHUSERFILE);
REM Cleanup: Remove temporary data;
&tmpfile = GetFile(&Sysfilename, "W", "A", %FilePath_Absolute);
&tmpfile.Delete();
In the above code, the value of /path/to/ftp/destination/ would depend on the FTP service configuration. &Sysfilename should contain the absolute path of the file on the server.
Approach 2: Upload file to intermediate database table
Because of the requirement of running a FTP service on the Application server, the first approach may not be a good option. In my experience, it would be tough convincing an administrator to setup a FTP service on a server machine just for the benefit of a single application. An alternative, yet also more portable option, is to upload the file to a database table. This option does not require additional configuration on the server. This is a two-step process though, and is not as efficient as the first option.
First, the file is uploaded to a database table. If the file is large, it may be chunked and stored across multiple rows. This process is documented thoroughly in the PeopleBook PeopleCode Developer’s Guide > Understanding File Attachments and PeopleCode. Now that the file is stored on a table, it can be retrieved later and saved to an Application server file prior to calling SendMail().
Step 1. Create a record for storing file attachments. In this example, we create a record and save it as FILE_STORE. Inside this record, only a single subrecord is added: FILE_ATTDET_SBR. Build/create this record. Note: if you’re running on DB2 OS390/zOS, check that Maximum Attachment Chunk Size option in PeopleTools Options page is set lower than the tablespace size of PS_FILE_STORE. The default Maximum Attachment Chunk Size is 28000, it is better to create PS_FILE_STORE in a 32K tablespace.
Once PS_FILE_STORE is created, the FieldChange PeopleCode for [Select Attachment] would be:
Declare Function add_attachment PeopleCode FILE_ATTACH_WRK.ATTACHADD FieldChange;
Component string &ATTACHSYSFILENAME, &ATTACHUSERFILE;
Component string &Sysfilename;
Local number &RetCode;
REM Select sysfilename prefix that will serve as uniqueness identifier;
&ATTACHSYSFILENAME = %UserId;
add_attachment("record://FILE_STORE", "", "", 0, False, "", &ATTACHSYSFILENAME, &ATTACHUSERFILE, 2, &RetCode);
Now, FieldChange PeopleCode for [Send] button would have addition steps for copying data from table to file and deleting rows from the table:
Component string &ATTACHSYSFILENAME, &ATTACHUSERFILE;
Local File &tmpfile;
Local integer &i;
REM Transfer data from DB table to local filesystem on Application server;
REM A check for latest version should be included in case user uploaded same file multiple times;
Local Rowset &rsAttachment = CreateRowset(Record.FILE_STORE);
&rsAttachment.Fill("where ATTACHSYSFILENAME = :1 AND VERSION = (SELECT MAX(VERSION) FROM PS_FILE_STORE WHERE ATTACHSYSFILENAME = FILL.ATTACHSYSFILENAME) order by FILE_SEQ", &ATTACHSYSFILENAME);
&tmpfile = GetFile(&ATTACHSYSFILENAME, "N");
Local String &Sysfilename = &tmpfile.Name;
For &i = 1 To &rsAttachment.RowCount
&tmpfile.WriteRaw(&rsAttachment(&i).FILE_STORE.FILE_DATA.Value);
End-For;
&tmpfile.Close();
SendMail(0, &MAIL_TO, &MAIL_CC, &MAIL_BCC, &MAIL_SUBJECT, &MAIL_TEXT, &Sysfilename, &ATTACHUSERFILE);
REM Cleanup: Remove temporary data;
SQLExec("DELETE FROM PS_FILE_STORE WHERE ATTACHSYSFILENAME = :1", &ATTACHSYSFILENAME);
&tmpfile = GetFile(&Sysfilename, "W", "A", %FilePath_Absolute);
&tmpfile.Delete();
Update
As noted by Ketan in the comments area, a better way to copy the attachment to the local filesystem is by using the GetAttachment() function. The FieldChange PeopleCode for the [Send] button would now look like this:
Component string &ATTACHSYSFILENAME, &ATTACHUSERFILE;
Local File &tmpfile;
Local string &filesyspath = GetEnv("PS_SERVDIR") | "/files";
REM Create PS_SERVDIR/files directory if it doesn't yet exists;
CreateDirectory(&filesyspath, %FilePath_Absolute);
&Sysfilename = &filesyspath | "/" | &ATTACHSYSFILENAME;
REM Transfer data from DB table to local filesystem on Application server;
If GetAttachment("record://FILE_STORE", &ATTACHSYSFILENAME, &Sysfilename) = 1 Then
/* Problem getting file */
End-If;
SendMail(0, &MAIL_TO, &MAIL_CC, &MAIL_BCC, &MAIL_SUBJECT, &MAIL_TEXT, &Sysfilename, &ATTACHUSERFILE);
REM Cleanup: Remove temporary data;
SQLExec("DELETE FROM PS_FILE_STORE WHERE ATTACHSYSFILENAME = :1", &ATTACHSYSFILENAME);
&tmpfile = GetFile(&Sysfilename, "W", "A", %FilePath_Absolute);
&tmpfile.Delete();
A thing to note about the above snippet is that line CreateDirectory(&filesyspath, %FilePath_Absolute); was added to ensure that PS_SERVDIR/files is available. Unlike GetFile(&ATTACHSYSFILENAME, "N"), GetAttachment() does not automatically create PS_SERVDIR/files directory if it does not yet exists in the application server.
Additional Notes
- In approach 1, you could assign a different machine as the destination FTP server. However, the path to the destination folder must be shared and accessible from the Application server. Deleting the file would then require the
DeleteAttachment()function. - In approach 1 and the application server is Unix-based, the user that runs the application server processes should also have write access to the ftp destination files. Otherwise, the use of
DeleteAttachment()function may also be required. Approach 2 should not have such limitations because the application server process should have full access to the default destination folder ofGetFile(). - For simplicity, the above code samples only deal with a single file attachment. It could be easily extended to handle multiple attachments.
SendMail()allows semicolon-separated list of values for the attachments.
February 21st, 2006 at 3:04 am
Great idea. I’ve used attachment functionality before, but I never thought about sending attachments through e-mail via PeopleCode.
I once worked on an upgrade from 7.5 to 8.8. The users didn’t want to loose their ability to run SQR on the client because they needed to load files that only resided on their hard drive.
I ended up using Approach #2 described above to create a run control page that let the user upload their file to the server. Then I created an app engine process to detach the file that they just uploaded to a staging directory. The users could send the file to NT or UNIX depending on where they ran the app engine process. After the file was on the server, they could run the SQR to import it.
February 21st, 2006 at 8:12 pm
Thanks a Bunch
February 22nd, 2006 at 3:26 am
In Approach 2 you can use peoplecode function getattachment to temporary store the file under PS_SERVDIR\files folder and send it using SendMail as an attachment. I have used file attachment functions for a batch process that needs to get a file from end user. A very good documentation exist in peoplebook.
February 27th, 2006 at 2:47 pm
This is simply great. I have used partly these but putting all the information in such a way, is great effort.
I need one more functionality in addition to attaching the file to email. I would like to use some password to open the attachment. This password can be user’s password to login into password. So code can pick this password from PSOPRDEFN.
Any ideas ????
Anurag
February 28th, 2006 at 1:02 am
Anurag,
The method of password-protecting a file will depend largely on the format of the file being attached. Perhaps you can simplify this if you zip the file with a password in the application server prior to sending the email. However, contrary to what you said, normally the password stored in PSOPRDEFN is hashed using a one-way encryption algorithm. You would not be able to retrieve user passwords from this table.
I suggest you let the user download the file from the PeopleSoft system instead:
Instead of attaching the file in the email sent, insert a hyperlink in the message. This hyperlink could be a link to a component or IScript page (IScript is more straighforward because you don’t really need any component functionality). When user clicks or navigate to the link, he will have to login to PeopleSoft first. This should solve your authentication issue.
Your link provided in the email could look like this:
http://ursite.com/servlets/psc/testsite/PORTAL/LOCAL/c/MISC.GET_ATTACH.GBL?sysfile=ATTACH1SYSNAME%2edoc&userfile=ATTACH1%2edoc
or
http://ursite.com/servlets/psc/testsite/PORTAL/LOCAL/s/WEBLIB_ATTACH.ISCRIPT1.FieldFormula.IScript_ShowAttachment?sysfile=ATTACH1SYSNAME%2edoc&userfile=ATTACH1%2edoc
In your IScript or component, use the ViewAttachment() function to serve the file to the user. The value of &ATTACHSYSFILENAME and &ATTACHUSERFILE could be retrieved using
&ATTACHSYSFILENAME = %Request.GetParameter("sysfile"); &ATTACHUSERFILE = %Request.GetParameter("userfile");In your code that generates the email, take out the lines that deletes the attachment from the system. You can opt to delete the file after it has been read by the user.
The code to generate the hyperlink URL for component is:
Local string &downloadlink = GenerateComponentContentURL(%Portal, %Node, MENUNAME.MISC, "GBL", COMPONENT.GET_ATTACH, PAGE.URPAGE, "U") | "?sysfile=" | EncodeURL(&ATTACHSYSFILENAME) | "&userfile=" | EncodeURL(&ATTACHUSERFILE);
For IScript:
Local string &downloadlink = GenerateScriptContentURL(%Portal, %Node, RECORD.WEBLIB_ATTACH, FIELD.ISCRIPT1, "FieldFormula", "IScript_ShowAttachment") | "?sysfile=" | EncodeURL(&ATTACHSYSFILENAME) | "&userfile=" | EncodeURL(&ATTACHUSERFILE);
Another thing, you should make sure &ATTACHSYSFILENAME is unique. Appending %UserID would no longer be applicable. Perhaps you could try using timestamp plus the user id of recipient.
For additional security — to prevent other users with the same role from retrieving a file not intended for them, you might want to generate a random password that you could add as a third parameter to your generated hyperlink. The idea is that you 1) generate the random password when you send the email, 2) store the password in table, 3) check the password when retrieving the file to make sure that it matches. If it doesn’t then don’t provide the download.
April 27th, 2006 at 5:32 am
I need to pass the with a domain user name and password to the ftp url.
like
ftp://domain\user:password@myurl.com
Since the ‘\’ is not permitted, my upload is failing with wrong credentials.
I tried
EncodeURL(”ftp://domain\user:password@myurl.com”)
as well as
EncodeURL(”ftp://domain%5Cuser:password@myurl.com”)
Both didn’t work.
Any clue folks
April 29th, 2006 at 1:37 am
I got it.
Supply ftp://”domain\user”:password@myurl.com.
Don’t use encodeurl.
This works.
July 2nd, 2006 at 12:02 pm
Hi ,
Can some help me in finding the complete path
of ATTACHUSERFILE from Subrecord.
Thanks in Advance
RRS
August 12th, 2006 at 4:48 pm
Thanks a bunch. The information you have provided is very useful. I have a situation here.
I am using the Sendmail() function to a mail the recipients in the Database using PeopleTools v8.17 (Application 8.0). I wanted to know if I could change the “From” mail ID using peoplecode depending on the Operator who has logged in while sending a mail. i.e.,When the mail is received by a recipient the “From” value should contain the Operator’s mail ID.
SendMail(0, &MAIL_TO, &MAIL_CC, &MAIL_BCC, &MAIL_SUBJECT, &MAIL_TEXT, &Sysfilename, &ATTACHUSERFILE);
Any help in this truly appreciated.
Kind Regards,
Santhosh Kumar.
August 14th, 2006 at 11:35 pm
Santhosh,
Unfortunately, overriding the From field is not supported in Sendmail of PeopleTools version below 8.4.
September 7th, 2006 at 2:24 pm
Hi,
we are currently doing an upgrade to Financials 89. We are considering of switching to the second approach regarding attaching documents to transactions. For the new transacttions this is no problem, but for the existing transactions we need to find a way to load the attachments in bulk into the BLOB column of the database record. This also would have to be able to chunck a file into the appropriate size. Has anyone done this before?
Ciao
September 8th, 2006 at 2:30 am
Thanks for the information.
It sounds like, in example 2, the file is saved into the FILE_ATTDET_SBR as chucks of data not as individual records.
Can the AddAttachment be used to load a .csv file directly into a PeopleSoft table with fields corresponding to the coma separarted fields on the .csv file. If so, do you have an example of PeopleCode that would do this.
Thanks,
Michael Swann
It sounds like the file is saved into the FILE_ATTDET_SBR as chucks of data not as individual records.
September 9th, 2006 at 8:48 am
Mike,
That is correct. AddAttachment is used for uploading any type of file to a specialized record.
And no, you can’t use AddAttachment to automatically load data to an application record. However AddAttachment could be useful to allow the end user to load the file to a repository, then the PeopleCode program could use GetAttachment to retrieve the file and process it.
February 13th, 2007 at 5:28 pm
As you said,AddAttachment is used for uploading any type of file to a specific record.
I have a requirement in which, i should be able to upload the documents in a specific location in the server and not into a record as raw data.
How can I do that?
Thanks
March 14th, 2007 at 5:10 am
ChiliJoe,
I just deployed the send email with multiple attachments by following your 2nd approach. It is working pretty well. I tested some big documents like Peoplesoft release notes. The attachments came out with the email without loosing single byte. Thank you very much for sharing your methods.
May 7th, 2007 at 6:24 pm
Hello,
We are currently experiencing problems with regards to attaching files in our development and training environments. Let us first focus on the development environment. We try uploading a file in the PeopleTools > Utilities > Debug > PeopleTools Test Utilities. First, we uploaded a test .csv file (1KB) using this sample URL, http://anonymous:anonymous@uswisuxapp3/opt/psoft/attachments/ …After 5 minutes, a security message displayed and when we tried clicking the Yes button, the page timed out. We tried looking into the application server and ftp log files… we found that the appserv log stopped at the “ExecutePutAttachment:starting, http://anonymous:anonymous@uswisuxapp2/opt/psoft/attachments/acdev1_test.csv, /tmp/PSFTP/A32435/acdev1_test.csv
ExecutePutAttachment:filtered name = http://anonymous:anonymous@uswisuxapp2/opt/psoft/attachments/acdev1_test.csv
ExecutePutAttachment:szTempDir = /tmp
ExecutePutAttachment: Checking if /tmp/ftp18753_26433_1178270288.txt exists.
ExecutePutAttachment: log file = /tmp/ftp18753_26433_1178270288.log”
In our training environment, we recreated the same process. The PeopleTools Test Utilities page was able to successfully attach the 1KB .csv file. On the other hand, when we tried to attach in one of our customized file attachment pages, a PeopleCode error message was encountered, “Attachment failed (137, 2)”.
Another scenario is trying to upload the same .csv file using this sample URL, record://PSFILE_ATTDET. The file was successfully attached using this URL. This URL works successfully on both environments.
*Note: The development environment has a different application server than our training environment.
I hope you can give us any ideas regarding the issue at hand. Thanks.
May 7th, 2007 at 10:42 pm
Hi Adrian,
Normally, the ftp protocol, instead of http, is specified for a ftp data storage system.
Other things you need to verify is that an ftp server is setup on the uswisuxapp2 machine correctly; and that the application server can connect to the ftp service on uswisuxapp2. The txt and log files (/tmp/ftp18753_26433_1178270288) may also provide additional information.
January 28th, 2008 at 2:42 am
Hi CJ,
Thanks for the notes. We are using Peoplesoft HCM 8.90.060 with Peopletools 8.46.17.
We’d really like to be able to raise that browse button dialog independently of any call to the AddAttachment function. The idea would be that the user could push the Browse button, and the selected filepath and filename would be returned to the edit box (and the record field of a derived or work record) for further processing, independently of any upload or other attach function.
So the dialog would look like the illustration you provided in your original post, except that it would appear directly on a page or subpage we build, and the Upload and Cancel buttons would not be there.
Have you ever heard of a way to do this?
January 29th, 2008 at 11:21 pm
David,
Check out the following (not working in firefox):
http://www.chili-mango.net/sandbox/filechooser.html
Is that close to what you want?
View source to see how it works. Just use the tips on [http://xtrahot.chili-mango.net/2007/04/mod-js/] to capture the result to a PS buffer field.
January 29th, 2008 at 11:48 pm
Ok, the following would work for both IE and firefox. It’s also closer to the one you initially described:
http://www.chili-mango.net/sandbox/fffilechooser.html
Also, in reflecting on these 2 solutions, I think it is necessary to remove the file input field from the html document prior to submission. This is to prevent the browser from sending the file to the server. Removal of the file input element can be done by javascript and on your PeopleSoft page can be accomplished by using the technique described here:
http://xtrahot.chili-mango.net/2007/05/overloading-submitscriptname/
March 5th, 2008 at 5:37 pm
An excellent article ! Thanks a bunch
March 14th, 2008 at 12:06 am
CJ,
Have you tried file attachment using SFTP in windows box?, because FTP is not a secured way to transfer.
Thanks.
March 15th, 2008 at 12:28 am
dbagent,
I haven’t tried SFTP, nor do I know if PeopleTools support it. I believe the FTP connection is made between your application server and the file (FTP) server machine. In most production environments, these servers should normally be outside the DMZ. As long as your web server is using SSL, then you should be ok.
May 2nd, 2008 at 7:37 pm
We are trying to attach doc, xls or pdf files via the user documentation page. We used the code that is delivered by PS with regards to LOAD/-JRNL_PNL…..But we are receiving a PeopleCode error message “Attachment failed (137, 2)”.
Though there is not much code changes..its only the URL that we changed…..Instead of PSFILE_ATTDET we customized into a record called C_JRNL_FILE_ATT which has the subrecord,FILE_ATTDET_SBR. Could some one help me in rectifying this error. A quick response is really appreciated.
Thanks in advance.
June 4th, 2008 at 5:40 pm
GOOD JOB ……….Keep it Up GUYS…..
October 28th, 2008 at 9:16 pm
A wonderful job guys, keep it up!!