Email an IBM i Output Queue as PDF
Email an OUTQ – should be simple right?
Here you are, you have this output queue filled with reports and you want to email them to your Boss/Colleague/whoever! You don’t really want them in smelly old text format you want them to look nice in something like PDF format…. That should be easy right?
IBM i should have a neat little option to do just that from the command line. Having said that, it kind of does, using IBM i Access but it’s manual and a little clunky to use.
So, lets make it real simple with our own CMD and CLP program to do just that
I’ve used this little EMLOUTQ utility program (written in IBM i Control Language) for ages and I just revisited it and did a quickie upgrade. After all, who doesnt like a quickie?
Code Compatibility
This example CLP works for IBM i 7.2+ it’s a lightly modernized using the new CPYSPLF command to copy it direct to a stream file before emailing.
If you are at V6R1M0 or V7R1M0 DONT PANIC this older version of EMLOUTQ does the same thing 🙂
EMLOUTQ OVERVIEW
Let our user enter a OUTQ, EMAIL address and select format of the emails (PDF is default). Press enter and have every *SPLF converted to PDF and emailed. Bish, Bosh, Thankyou Ma’am.
Source code is simple its a CMD and CLP
Copy paste it, compile it and boom check it out 🙂
EMLOUTQ Command
Source code for the command looks like this:
CMD PROMPT(' EMAIL OUTQ ')
PARM KWD(OUTQ) TYPE(OUTQ) MIN(1) PROMPT('Output queue')
OUTQ: QUAL TYPE(*NAME) LEN(10) MIN(1) EXPR(*YES)
QUAL TYPE(*NAME) LEN(10) DFT(*LIBL) SPCVAL((*LIBL +
*LIBL) (*CURLIB *CURLIB)) PROMPT('Library name')
PARM KWD(FLR) TYPE(*CHAR) LEN(100) DFT(*TMP) +
SPCVAL((*TMP '/tmp')) EXPR(*YES) +
CASE(*MIXED) PMTCTL(*PMTRQS) +
PROMPT('Working IFS Folder for *PDF')
PARM KWD(RCP) TYPE(*CHAR) LEN(100) MIN(1) +
EXPR(*YES) CASE(*MIXED) PROMPT('Email Address')
PARM KWD(SUBJECT) TYPE(*CHAR) LEN(100) +
DFT('EMAIL FROM IBM i POWER SYSTEM') ALWVAR(*YES) +
EXPR(*YES) CASE(*MIXED) PMTCTL(*PMTRQS) +
PROMPT('Email Subject')
PARM KWD(ATTACH) TYPE(*CHAR) LEN(4) RSTD(*YES) +
DFT(*TXT) VALUES(*PDF *TXT) EXPR(*YES) +
PMTCTL(*PMTRQS) PROMPT('Format of *SPLF attachment')
PARM KWD(IGNOREHLD) TYPE(*CHAR) LEN(4) RSTD(*YES) +
DFT(*NO) VALUES(*YES *NO) EXPR(*YES) +
PMTCTL(*PMTRQS) PROMPT('Ignore *SPLF with STATUS(HLD)')
PARM KWD(DELETE) TYPE(*CHAR) LEN(4) RSTD(*YES) +
DFT(*YES) VALUES(*YES *NO) EXPR(*YES) +
PMTCTL(*PMTRQS) PROMPT('Delete *SPLF after emailing')
Copy this into your QCMDSRC and compile it with CRTCMD CMD(MYLIBRARY/EMLOUTQ) PGM(MYLIBRARY/EMLOUTQ) SRCFILE(MYSRCLIB/QCMDSRC)
EMLOUTQ Command – CLP Processor
/*-------------------------------------------------------------------------*/
/* */
/* Program: EMLOUTQ.CLLE IBM i V7.2+ */
/* */
/* Desc: Generate a list of all spool files in an output queue and email */
/* Annoyingly, WRKOUTQ does not have an output(*OUTFILE) option so */
/* lets user APIs to process all SPLF from a specific OUTQ. */
/* */
/* Author: nick litten */
/* */
/* Date: October 4th 2017 */
/* */
/*-------------------------------------------------------------------------*/
/* Nick Litten May 7th 2021 */
/* Fix 20 char fields to ensure blanks are not truncated */
/*-------------------------------------------------------------------------*/
PGM PARM(&OUTQPARM &IFS_FLR &EMAIL &SUBJECT &TYPE &IGNOREHLD +
&DELETE)
COPYRIGHT TEXT('EMLOUTQ Email all *SPLFS from Outq - Ver.001')
DCL VAR(&OUTQPARM) TYPE(*CHAR) LEN(20)
DCL VAR(&OUTQ) TYPE(*CHAR) LEN(10)
DCL VAR(&OUTQLIB) TYPE(*CHAR) LEN(10)
DCL VAR(&TYPE) TYPE(*CHAR) LEN(4)
DCL VAR(&DELETE) TYPE(*CHAR) LEN(4)
DCL VAR(&IGNOREHLD) TYPE(*CHAR) LEN(4)
DCL VAR(&COUNT) TYPE(*DEC) LEN(7 0)
DCL VAR(&OCTET) TYPE(*CHAR) LEN(10)
DCL VAR(&BIN) TYPE(*CHAR) LEN(10)
DCL VAR(&USERDATA) TYPE(*CHAR) LEN(10) VALUE('*EMAILED') /* SPLF +
will change to this userdata after emailing */
DCL VAR(&IFS_FLR) TYPE(*CHAR) LEN(100) /* IFS Folder to store the +
converted *SPLF PDF's */
DCL VAR(&IFS_SPLF) TYPE(*CHAR) LEN(200) /* full name of PDF in +
format /folder/document.pdf */
DCL VAR(&EMAIL) TYPE(*CHAR) LEN(100) /* Email receiptient - email +
address in form somebody@somewhere.com */
DCL VAR(&SUBJECT) TYPE(*CHAR) LEN(100) /* Title of email - ie: +
"Email from IBM POWER SYSTEM" */
DCL VAR(&EMAILBODY) TYPE(*CHAR) LEN(200) VALUE('<h1>this is the +
body of the email in HTML format</h1>') /* Title of email - +
ie: "Email from IBM POWER SYSTEM" */
DCL VAR(&SYSTEMNAME) TYPE(*CHAR) LEN(8)
DCL VAR(&FILE) TYPE(*CHAR) LEN(10)
DCL VAR(&JOBNAME) TYPE(*CHAR) LEN(10)
DCL VAR(&USER) TYPE(*CHAR) LEN(10)
DCL VAR(&JOBNUMBER) TYPE(*CHAR) LEN(6)
DCL VAR(&STATUS) TYPE(*CHAR) LEN(10)
DCL VAR(&SPLNBR) TYPE(*DEC) LEN(4)
DCL VAR(&SPLNBRALF) TYPE(*CHAR) LEN(4)
DCL VAR(&MSGID) TYPE(*CHAR) LEN(7)
DCL VAR(&MSGDTALN) TYPE(*DEC) LEN(9 0)
DCL VAR(&MSGDTA) TYPE(*CHAR) LEN(80)
DCL VAR(&NO_ERROR) TYPE(*CHAR) LEN(4) VALUE(X'00000000')
DCL VAR(&DFTUSNAME) TYPE(*CHAR) LEN(10) VALUE('UPD_UDTA')
DCL VAR(&DFTUSLIB) TYPE(*CHAR) LEN(10) VALUE('QTEMP')
/* Declares for QUSCRTUS - Create User Space */
DCL VAR(&QCUS_NAME) TYPE(*CHAR) LEN(20)
DCL VAR(&QCUS_EXATR) TYPE(*CHAR) LEN(10) VALUE('USRSPC ')
/* Maximum number of SPLF the User Space will contain */
DCL VAR(&QCUS_SIZE) TYPE(*CHAR) LEN(4) VALUE(X'00010000')
DCL VAR(&QCUS_INIT) TYPE(*CHAR) LEN(1) VALUE(X'00')
DCL VAR(&QCUS_PUBA) TYPE(*CHAR) LEN(10) VALUE('*ALL ')
DCL VAR(&QCUS_TEXT) TYPE(*CHAR) LEN(50)
DCL VAR(&QCUS_REPL) TYPE(*CHAR) LEN(10) VALUE('*YES ')
DCL VAR(&QCUS_DOMN) TYPE(*CHAR) LEN(10) VALUE('*DEFAULT ')
/* Declares for QUSLSPL - List Spooled Files */
DCL VAR(&QLSF_NAME) TYPE(*CHAR) LEN(20)
DCL VAR(&QLSF_FOMT) TYPE(*CHAR) LEN(8) VALUE('SPLF0100')
DCL VAR(&QLSF_USER) TYPE(*CHAR) LEN(10)
DCL VAR(&QLSF_OUTQ) TYPE(*CHAR) LEN(20)
DCL VAR(&QLSF_FORM) TYPE(*CHAR) LEN(10)
DCL VAR(&QLSF_USRD) TYPE(*CHAR) LEN(10)
/* Declares for QUSRTVUS - Retrieve User Space */
DCL VAR(&QRUS_NAME) TYPE(*CHAR) LEN(20)
DCL VAR(&QRUS_STRT) TYPE(*CHAR) LEN(4)
DCL VAR(&QRUS_LENG) TYPE(*CHAR) LEN(4)
DCL VAR(&QRUS_HEAD) TYPE(*CHAR) LEN(16)
DCL VAR(&QRUS_LINE) TYPE(*CHAR) LEN(82)
DCL VAR(&INT_OFFSET) TYPE(*DEC) LEN(9 0)
DCL VAR(&INT_NUMBER) TYPE(*DEC) LEN(9 0)
DCL VAR(&INT_SIZE) TYPE(*DEC) LEN(9 0)
DCL VAR(&INT_POSIT) TYPE(*DEC) LEN(9 0)
/* Variables QUSRSPLA - Get Spooled File Attributes */
DCL VAR(&QGSA_RCV) TYPE(*CHAR) LEN(3772)
DCL VAR(&QGSA_RCVLN) TYPE(*CHAR) LEN(4) VALUE(X'00000EBC')
DCL VAR(&QGSA_FOMT) TYPE(*CHAR) LEN(8) VALUE('SPLA0200')
DCL VAR(&QGSA_JOB) TYPE(*CHAR) LEN(26) VALUE('*INT ')
DCL VAR(&QGSA_IJOB) TYPE(*CHAR) LEN(16)
DCL VAR(&QGSA_ISPL) TYPE(*CHAR) LEN(16)
DCL VAR(&QGSA_SPLF) TYPE(*CHAR) LEN(10) VALUE('*INT ')
DCL VAR(&QGSA_SPLNB) TYPE(*CHAR) LEN(4) VALUE(X'00000000')
/* Declares for QUSDLTUS - Delete User Space */
DCL VAR(&QDUS_NAME) TYPE(*CHAR) LEN(20)
/* Declares for QERRCD - Error Code */
DCL VAR(&QERRCD) TYPE(*CHAR) LEN(96)
/* construct the 20 char API values for OBJECT and NAME */
CHGVAR VAR(&QDUS_NAME) VALUE(&DFTUSNAME *CAT &DFTUSLIB)
CHGVAR VAR(&QRUS_NAME) VALUE(&DFTUSNAME *CAT &DFTUSLIB)
CHGVAR VAR(&QLSF_NAME) VALUE(&DFTUSNAME *CAT &DFTUSLIB)
CHGVAR VAR(&QCUS_NAME) VALUE(&DFTUSNAME *CAT &DFTUSLIB)
CHGVAR VAR(&TYPE) VALUE(%SUBSTRING(&TYPE 2 3)) /* Chop of the '*' +
from front of parm */
CHGVAR VAR(&OUTQ) VALUE(&OUTQPARM)
CHGVAR VAR(&OUTQLIB) VALUE(%SUBSTRING(&OUTQPARM 11 10))
RTVNETA SYSNAME(&SYSTEMNAME)
CHGVAR VAR(&SUBJECT) VALUE(&SUBJECT *BCAT '|System:' *TCAT +
&SYSTEMNAME *TCAT ' Outq:' *TCAT &OUTQ)
/* Create Userspace */
CHGVAR VAR(%SST(&QERRCD 1 8)) VALUE(X'0000006000000000')
CALL PGM(QUSCRTUS) PARM(&QCUS_NAME &QCUS_EXATR &QCUS_SIZE +
&QCUS_INIT &QCUS_PUBA &QCUS_TEXT &QCUS_REPL &QERRCD +
&QCUS_DOMN)
IF COND(%SST(&QERRCD 5 4) *NE &NO_ERROR) THEN(GOTO +
CMDLBL(ERROR_API))
/* List all Spooled Files from a given Output Queue */
CHGVAR VAR(&QLSF_USER) VALUE('*ALL')
CHGVAR VAR(&QLSF_OUTQ) VALUE(&outq *CAT &outqlib)
CHGVAR VAR(&QLSF_FORM) VALUE('*ALL')
CHGVAR VAR(&QLSF_USRD) VALUE('*ALL')
CHGVAR VAR(%SST(&QERRCD 1 8)) VALUE(X'0000006000000000')
CALL PGM(QUSLSPL) PARM(&QLSF_NAME &QLSF_FOMT &QLSF_USER &QLSF_OUTQ +
&QLSF_FORM &QLSF_USRD &QERRCD)
IF COND(%SST(&QERRCD 5 4) *NE &NO_ERROR) THEN(GOTO +
CMDLBL(ERROR_API))
/* Read Headerdata from Userspace */
CHGVAR VAR(%BIN(&QRUS_STRT)) VALUE(125)
CHGVAR VAR(%BIN(&QRUS_LENG)) VALUE(16)
CHGVAR VAR(%SST(&QERRCD 1 8)) VALUE(X'0000006000000000')
CALL PGM(QUSRTVUS) PARM(&QRUS_NAME &QRUS_STRT &QRUS_LENG &QRUS_HEAD +
&QERRCD)
IF COND(%SST(&QERRCD 5 4) *NE &NO_ERROR) THEN(GOTO +
CMDLBL(ERROR_API))
CHGVAR VAR(&INT_OFFSET) VALUE(%BIN(&QRUS_HEAD 1 4))
CHGVAR VAR(&INT_NUMBER) VALUE(%BIN(&QRUS_HEAD 9 4))
CHGVAR VAR(&INT_SIZE) VALUE(%BIN(&QRUS_HEAD 13 4))
/* Loop through the Userspace */
CHGVAR VAR(&INT_POSIT) VALUE(0)
LOOP_SPACE: CHGVAR VAR(&INT_POSIT) VALUE(&INT_POSIT + 1)
IF COND(&INT_POSIT *GT &INT_NUMBER) THEN(GOTO CMDLBL(END_SPACE))
/* Read next Userspace detail line */
CHGVAR VAR(%BIN(&QRUS_STRT)) VALUE(&INT_OFFSET + ((&INT_POSIT - 1) * +
&INT_SIZE) + 1)
CHGVAR VAR(%BIN(&QRUS_LENG)) VALUE(82)
CHGVAR VAR(%SST(&QERRCD 1 8)) VALUE(X'0000006000000000')
CALL PGM(QUSRTVUS) PARM(&QRUS_NAME &QRUS_STRT &QRUS_LENG &QRUS_LINE +
&QERRCD)
IF COND(%SST(&QERRCD 5 4) *NE &NO_ERROR) THEN(GOTO +
CMDLBL(ERROR_API))
/* Get Spooled File Attributes */
CHGVAR VAR(&QGSA_IJOB) VALUE(%SST(&QRUS_LINE 51 16))
CHGVAR VAR(&QGSA_ISPL) VALUE(%SST(&QRUS_LINE 67 16))
CHGVAR VAR(%SST(&QERRCD 1 8)) VALUE(X'0000006000000000')
CALL PGM(QUSRSPLA) PARM(&QGSA_RCV &QGSA_RCVLN &QGSA_FOMT &QGSA_JOB +
&QGSA_IJOB &QGSA_ISPL &QGSA_SPLF &QGSA_SPLNB &QERRCD)
IF COND(%SST(&QERRCD 5 4) *NE &NO_ERROR) THEN(GOTO +
CMDLBL(ERROR_API))
CHGVAR VAR(&JOBNAME) VALUE(%SST(&QGSA_RCV 49 10))
CHGVAR VAR(&FILE) VALUE(%SST(&QGSA_RCV 75 10))
CHGVAR VAR(&USER) VALUE(%SST(&QGSA_RCV 59 10))
CHGVAR VAR(&JOBNUMBER) VALUE(%SST(&QGSA_RCV 69 10))
CHGVAR VAR(&SPLNBR) VALUE(%BIN(&QGSA_RCV 85 4))
CHGVAR VAR(&STATUS) VALUE(%sst(&QGSA_RCV 110 4))
/* Ignore *SPLF if they are HOLD(*YES) if selected */
IF COND(&IGNOREHLD *EQ *YES *AND &STATUS *EQ 'HELD') THEN(GOTO +
CMDLBL(LOOP_SPACE))
/* construct unique name for SPLF in PDF format */
CHGVAR VAR(&SPLNBRALF) VALUE(&SPLNBR)
CHGVAR VAR(&IFS_SPLF) VALUE(&IFS_FLR *TCAT '/EMLOUTQ from' *BCAT +
&SYSTEMNAME *TCAT ' outq' *BCAT &OUTQ *TCAT ' file' *BCAT +
&FILE *TCAT ' number' *BCAT &SPLNBRALF *TCAT '.' *TCAT &TYPE)
CHGVAR VAR(&EMAILBODY) VALUE('<H1>IBM i SPOOL EMAIL</H1><P>Spool +
file' *BCAT &IFS_SPLF *TCAT ' is attached in PDF +
format.</p>')
/* copy spool into IFS location as a PDF */
IF COND(&TYPE *EQ 'TXT') THEN(DO)
CPYSPLF FILE(&FILE) TOFILE(*TOSTMF) JOB(&JOBNUMBER/&USER/&JOBNAME) +
SPLNBR(&SPLNBR) TOSTMF(&IFS_SPLF) WSCST(*NONE) +
STMFOPT(*REPLACE)
CHGVAR VAR(&OCTET) VALUE('*PLAIN')
CHGVAR VAR(&BIN) VALUE('*TXT')
ENDDO
ELSE CMD(DO)
CPYSPLF FILE(&FILE) TOFILE(*TOSTMF) JOB(&JOBNUMBER/&USER/&JOBNAME) +
SPLNBR(&SPLNBR) TOSTMF(&IFS_SPLF) WSCST(*PDF) +
STMFOPT(*REPLACE)
CHGVAR VAR(&OCTET) VALUE('*OCTET')
CHGVAR VAR(&BIN) VALUE('*BIN')
ENDDO
/* send PDF as email */
SNDSMTPEMM RCP((&EMAIL *PRI)) SUBJECT(&SUBJECT) NOTE(&EMAILBODY) +
ATTACH((&IFS_SPLF &OCTET &BIN)) CONTENT(*HTML)
MONMSG MSGID(CPF0000) EXEC(DO)
SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) MSGDTA('* Email NOT sent to' +
*BCAT &EMAIL *TCAT ' with *PDF of' *BCAT &IFS_SPLF) +
TOPGMQ(*PRV) MSGTYPE(*COMP)
GOTO CMDLBL(LOOP_SPACE)
ENDDO
CHGVAR VAR(&COUNT) VALUE(&COUNT + 1)
SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) MSGDTA('Email sent to' *BCAT +
&EMAIL *TCAT ' with *' *TCAT &TYPE *BCAT 'of' *BCAT +
&IFS_SPLF) TOPGMQ(*PRV) MSGTYPE(*COMP)
IF COND(&DELETE *EQ *YES) THEN(DO)
DLTSPLF FILE(&FILE) JOB(&JOBNUMBER/&USER/&JOBNAME) SPLNBR(&SPLNBR)
RMVLNK OBJLNK(&IFS_SPLF)
ENDDO
ELSE CMD(DO)
CHGSPLFA FILE(&FILE) JOB(&JOBNUMBER/&USER/&JOBNAME) SPLNBR(&SPLNBR) +
SAVE(*NO) USRDTA(&USERDATA)
ENDDO
/* Read next Spooled File from User Space */
GOTO CMDLBL(LOOP_SPACE)
/* Delete Userspace */
END_SPACE: CHGVAR VAR(%SST(&QERRCD 1 8)) VALUE(X'0000006000000000')
CALL PGM(QUSDLTUS) PARM(&QDUS_NAME &QERRCD)
IF COND(%SST(&QERRCD 5 4) *NE &NO_ERROR) THEN(GOTO +
CMDLBL(ERROR_API))
ENDPROGRAM: RETURN
/* API-Error */
ERROR_API: CHGVAR VAR(&MSGID) VALUE(%SST(&QERRCD 9 7))
CHGVAR VAR(&MSGDTALN) VALUE(%BIN(&QERRCD 5 4))
CHGVAR VAR(&MSGDTALN) VALUE(&MSGDTALN - 16)
CHGVAR VAR(&MSGDTA) VALUE(%SST(&QERRCD 17 &MSGDTALN))
SNDPGMMSG MSGID(&MSGID) MSGF(QCPFMSG) MSGDTA(&MSGDTA) MSGTYPE(*DIAG)
/* Error */
ERROR: SNDPGMMSG MSGID(CPF9899) MSGF(QCPFMSG) MSGTYPE(*ESCAPE)
ENDPGM: ENDPGM
NOTE variable VAR(&QDUS_NAME) is a 20 char field with the userspace name in it. This userspace is a temporary storage area used during the *SPLF to *PDF conversion. It is defined as called UPD_UDTA at posn1 and the library QTEMP at posn 11 – this is because this is how the IBM i *API’s refer to addresses for object NAME in posn(1-10) and LIB in posn (11-20)
Compile Instructions
Compile the command with defaults like this:
CRTCMD CMD(mylib/EMLOUTQ) PGM(*LIBL/EMLOUTQ) SRCFILE(mylib/QCMDSRC) SRCMBR(EMLOUTQ)
and then compile the Program like this:
CRTBNDCL PGM(mylib/EMLOUTQ) SRCFILE(mylib/QCLLESRC) SRCMBR(EMLOUTQ) TGTRLS(V7R2M0)
and enter the command to play with it:
EMLOUTQ OUTQ(MYOUTQ) RCP(nick.litten@somewhere.com) SUBJECT('These are some scrummy PDF emails') ATTACH(*PDF) IGNOREHLD(*NO) DELETE(*YES)
Thats all Folks!
UPDATE May 2021: calling the CL from an RPGLE program
I would use QCMDEXC, or system(), to call the EMLOUTQ *CMD directly. But, you could also call the CL program itself something like this:
dcl-pr emailOutputQueue extpgm('EMLOUTQ');
*n char(20); // outq
*n char(100); // folder
*n char(100); // recipient
*n char(100); // subject
*n char(4); // attach
*n char(4); // ignoreheld
*n char(4); // delete
end-pr;
dcl-s outq char(20);
dcl-s outqname char(10) inz('PRT01');
dcl-s outqlib char(10) inz('*LIBL');
dcl-s folder char(100) inz('/tmp');
dcl-s recipient char(100) inz('name@somewhere.com');
dcl-s subject char(100) inz('this is the email subject');
dcl-s attach char(4) inz('*PDF');
dcl-s ignoreheld char(4) inz('*NO');
dcl-s delete char(4) inz('*NO');
then your mainline might look like this:
outq = outqname + outqlib;
callp(e) emailOutputQueue ( outq : folder : recipient : subject : attach : ignoreheld : delete );
if %error();
// email was not sent so add error handling here
else;
// email successful so notify user that email was sent to 'recipient'?
endif
Thanks for the sample code …..there are few issues ……
what is this ‘UPD_UDTA QTEMP’? this gives an error
if you call this via program ….then how do you define parameters (appreciate if you can give some examples parameter by parameter)
That is a USER SPACE that is created in QTEMP. It shouldn’t give an error but make sure it’s defined in your source code as a 20 character field with UP_UDTA at posn1 and QTEMP at posn 11. Does this fix it?
I cut/paste entire code without any changes and successfully compile…..but when I call this program in command prompt …system gives error saying something wrong with UP_UDTA ….I think since I pass all parameter in command line …I messed up some values…..
So…Do I have to change something at posn1 and QTEMP at position 11 before I call this?
Also, what should I pass as parameter to make it work
I’ve just added a code change to get around this copy/paste problem for you… copy the CLLE again and it should work. You can see the new fields I added – &DFTUSNAME and &DFTUSLIB
PS: writing an RPGLE prototype to wrap around the command is easy. I will code this up in a blog tomorrow. 🙂
PS: you can watch EMLOUTQ code review and see it in action here https://www.nicklitten.com/lessons/emloutq-whats-in-a-control-language-program/