Friday, September 11, 2026

OpenVMS DCL: Sending Log Files as Email Attachments Using MIME

Sending a True Email Attachment from OpenVMS Using MAIL and MIME


After spending three days figuring this out, I thought I'd document the solution for the next OpenVMS administrator who needs it.

My goal was simple:

Send an e-mail from a command procedure

Include a short message body

Attach a .LOG file as a real attachment

Use only native OpenVMS tools


Sounds easy. It wasn't.


Environment

OpenVMS V8.4-2L3

TCP/IP Services V5.7-13ECO5F

SYS$SYSTEM:MIME.EXE


First Discovery

This works:

$ MAIL/SUBJECT="Refresh PROMSYS1 Log" REFRESH_PROMSYS1.LOG smtp%"user@company.com"


However, the contents of the log file become the body of the e-mail.

You do not get an attachment.


MIME Is Required

To send a true attachment, OpenVMS provides the MIME utility:

$ MIME


Interactive Method That Works

Start MIME:

$ MIME


Create a new message:

MIME> NEW BODY.TXT


When the editor opens, enter:

Refresh completed successfully.


Press:

Ctrl/Z


You'll see:

*


Then:

EXIT


Back at the MIME prompt:

MIME> ADD REFRESH_PROMSYS1.LOG


Verify:

MIME> LIST


You should see:

Attachment: 1

Content-Disposition: inline

Attachment: 2

Content-Disposition: attachment

 

Save:

MIME> SAVE

MIME> EXIT


Send:

$ MAIL/NOSELF BODY.TXT smtp%"user@company.com" /SUBJECT="Refresh PROMSYS1 Log"

 

Result:

E-mail body contains:

Refresh completed successfully.

REFRESH_PROMSYS1.LOG arrives as a real attachment.


Success.


The Problems Encountered

The MIME documentation suggests:

MIME> NEW/NOEDIT BODY.TXT


Unfortunately, on my system, this did not behave the same way as:

MIME> NEW BODY.TXT


Typical symptoms:

  • Body text missing
  • Attachment present
  • MIME structure looks valid
  • Generated e-mail arrives with a blank body


I spent quite a bit of time trying:

NEW/NOEDIT


DCL-created text files:

$ OPEN/WRITE BODY BODY.TXT

$ WRITE BODY "Refresh completed successfully."

$ CLOSE BODY


Command files:

$ MIME < MIME.CMD


Variable substitution:

ADD 'LOGFILE'


Most combinations failed in one way or another.


The Key Discovery

A MIME draft is not the same thing as a text file.


The file created by:

MIME> NEW BODY.TXT


contains MIME structure that is preserved when reopened later using:

OPEN/DRAFT


That turned out to be the key.


One-Time Setup

Create a reusable MIME template.


Start MIME:

$ MIME


Create:

MIME> NEW BODY_BASE.TXT


Enter:

Refresh completed successfully.


Then:

Ctrl/Z

EXIT


Save:

MIME> SAVE

MIME> EXIT


Keep this file permanently.


Working Automated Solution

This is the command procedure I ended up using.

$ OLD_VERIFY = F$VERIFY(1)

$ WRITE SYS$OUTPUT "Sending out notification mail..."

$ DELETE/LOG BODY.TXT;*

$ COPY BODY_BASE.TXT BODY.TXT

$ MIME

OPEN/DRAFT BODY.TXT

ADD REFRESH_PROMSYS1.LOG

SAVE

EXIT

$ MAIL/NOSELF BODY.TXT smtp%"user@company.com" /SUBJECT="Refresh PROMSYS1 Log"

$ WRITE SYS$OUTPUT "Mail sent."

$ DUMMY = F$VERIFY(OLD_VERIFY)

$ EXIT


Result:

  • E-mail body contains:

Refresh completed successfully.

  • REFRESH_PROMSYS1.LOG arrives as an attachment.


Exactly what I needed.


Lessons Learned

What Works


Create a MIME draft once:

BODY_BASE.TXT


Reuse it:

COPY BODY_BASE.TXT BODY.TXT

OPEN/DRAFT BODY.TXT

ADD logfile

SAVE


Send:

MAIL/NOSELF BODY.TXT


What Doesn't Work Reliably

Creating BODY.TXT directly from DCL:

$ OPEN/WRITE BODY BODY.TXT

$ WRITE BODY "Refresh completed successfully."

$ CLOSE BODY


This creates a text file, but not a MIME draft.


Most Important Lesson

After three days of testing, I came to the following conclusion:

If it works, stop improving it.


Summary

The solution that finally worked was:

BODY_BASE.TXT

        |

        +--> COPY BODY.TXT

        |

        +--> OPEN/DRAFT

        +--> ADD REFRESH_PROMSYS1.LOG

        +--> SAVE

        +--> MAIL


The key was understanding that MIME draft files created by MIME are special and cannot simply be replaced by ordinary text files.


Hopefully this saves the next OpenVMS administrator three days of trial and error. 😊


Saturday, March 7, 2026

Maximum IF-THEN Levels Includes All IF-THEN Clauses

08-Mar-2026

Last Friday, I was doing an update to an existing command procedure. I needed to make a continuous check for values comparing it with a reference value.


I knew aboug that maximum depth for the IF-THEN clause. What I didn't know is that it includes any subsequent IF-THEN clauses within an enclosing IF-THEN clause.


Anyway, when I tested the program, I saw the error, UNDEFINED IF-THEN% something-something, and I checked where I have done the change.


I caught it. It was the long IF-THEN clauses, but this time, there is already the IF-THEN clause at the top level. How did I got it fixed? 


Good thing there is GOSUB.


I found a nice location where it won't affect the rest of the loops and all, et voila!


So, there you have it.


Maximum IF-THEN levels is still 15, and it applies to the enclosing IF-THEN clause down to subsequent IF-THEN clauses. Use GOSUB, or any other applicable lexicals to effect the logic you need.


Till then!

Saturday, January 17, 2026

How do I get the DCL variable into PCL?

17-Jan-2025


How do I get the DCL variable into PCL?


When creating scripts, you usually switch between PROMIS and VMS. You write Scripts (.scr) and command procedures (.com), and you extract data either by running PROMIS scripts or by running TP commands. And 99.9% of the time, evaluation and decision-making is at the DCL level.


When you have the set of commands and variables that you want to run in PROMIS, how do you make these available in PROMIS (PCL)?


Quite simple, actually. And you would feel it silly once you know it.


Here's how. Really simple.


Let's say in DCL, you have a variable named var:

$ var :== this_value


At this points, this is only available in DCL, and when you go up to PCL, issuing %var will result in error.


To make 'var' variable available in PCL, just prefix it with 'pcl_'. Yes, that simple.


So

$ var == this_value

becomes

$ pcl_var == this_value

and then when you issue %var in PCL, the value will be resolved.


No, you don't use %pcl_var in PCL, only %var.


So what if you want to retain var variable in DCL?


Just add a line to assign the value of var into another variable, but that variable has to have 'pcl_' prefix, like this:

$ var == this_value

$ pcl_pvar == ''var'


or, if you want string value, just add double quotation mark before and after the assignment, like so:

$ pcl_pvar == "''var'"


Then at PCL, you can use '%pvar' and the value is resolved.


Alright. That's it for now. Hope this helps.


Till then!