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!


Friday, February 28, 2025

OpenVMS IF-THEN-ELSE Maximum Depth, Levels

Up to what depth or level can you do for OpenVMS IF-THEN-ELSE?


Just the other day, I found out (for good!) that OpenVMS IF-THEN-ELSE has a limit on the depth or level that will work properly. What is the meaning of this?


Simply, if you have many conditions to check, and you do a multiple IF-THEN-ELSE, or what we would also call a NESTED IF in Object-Oriented Programming (OOP), the limit of level or depth that you can do in OpenVMS is?


15.


Yes, 15.


I found out about this the hard way. I have been running a script for many days already, actually months, and I had no clue whatsoever. I just know that some checks, especially those recently added, well, they don't seem to work. I thought they do, and the error is happening intermittently. And that would be harder to troubleshoot.


But one fine day, as I was requested to run the same OpenVMS script more and more, like it was every week, then daily, and recently, many times in a day.


That gave me more chance to see why the script is 'intermittently ' -- or so I thought -- failing. When I scrolled the screen back up, I saw some 'invalid IF' error. I put some 'write sys$output' on each check to know where the failure is happening.


Before that (the 'write sys$output'), I was having a hard time scrolling back and forth, up and down, what could be causing the 'invalid IF', but found none.


Finding none, I thought of putting the 'write sys$output' line on each check, before each check is done.


Et voila!


The check continues to print the check, but I found that after the 15th IF under ELSE, the 'invalid IF' error is thrown. That gave me the idea, so I split the IF-THEN-ELSE clauses into different sets.


First set is up to 15 IF-ELSE, then a check is added to verify the placeholder if having a value, which is supposed to be populated when a match is found. So if no value is assigned, the second set of 15 IF-THEN-ELSE clauses is entered.


I don't have more than 30 conditions to check, so if I have, it will be from 31 to 45 IF-THEN-ELSE clauses.


So there you have it. 15.


How many levels of IF-THEN-ELSE clauses can you nest together? Up to 15 only.


I hope that this helps all fellow OpenVMS enthusiasts out there, encountering the same issue as I did, but found the fix in time.


Thank you and till then!


Friday, May 17, 2024

Back to VMS-PROMIS

After 10 years doing many other things, I am back to PROMIS running in VMS. At the start of the year I am doing a total recall, and muscle memory kicks in now and then, starting with my account name, which is one letter different. Had to get used to it ASAP, and all the other scripts that I used to run I had to recreate.


This time around, I will need to really document my learning. Will do it.


Till then.


Monday, May 11, 2015

Questions in PROMIS? Ask Me!

English: The logo of Oracle Corporation de:Bil...
English: The logo of Oracle Corporation de:Bild:Oracle-Logo.svg he:תמונה:Oracle Logo.jpg (Photo credit: Wikipedia)
English: This chart represents several constit...
English: This chart represents several constituent components of the SQL language in a single SQL Update (SQL) statement Ελληνικά: Γλωσσικά στοιχεία σε ένα statement SQL (Photo credit: Wikipedia)
12-May-2015


Hi guys!

Been some time since I posted in my blog. I am doing online business now, and I find that this is taking a lot of my time, even at home. I can't concentrate anymore on my blogs.

Not the full concentration I had then.

I ask therefore, that if you have anything you want to know about PROMIS, please drop me a comment, a question.

Then I will post the answer.

That is actually how I started in VB6 and SQL, and Oracle 10. I didn't know, but the opportunity to learn was there, and there were expert colleagues - who were not selfish nor stingy - and answered patiently every question that I raised, and even more...

Database Normalization eBook

Then my boss gave me a huge project, which I built slowly and steadily, asking even more and more questions, until I had to decide to buy an e-book, which gave me more idea, leading to more questions, and so on and so forth, until I was already designing and building applications and systems - alone.

Okay, so I believe I have put the message across: please ask me questions, and I will try to answer as objectively as possible.

Till then, thank you!

Thursday, October 30, 2014

Coming up...

Inner view of a Seagate 3.5 inches hard disk d...
Inner view of a Seagate 3.5 inches hard disk drive Medalist ST33232A model manufactured in Malaysia in 1998. Parallel ATA interface, ultra DMA mode 2, I/O data-transfer rate 33.3 MB/s max, 3,227 MB, 3 platters, 6 physical read/write heads, spindle speed 4,500 RPM, cache buffer 128 kB, MTBF 300,000 hours, dimensions 146.8 x 102.4 x 26.2 mm, weight 540 grams. (Photo credit: Wikipedia)
30-October-2014


I saved all my scripts and command files in a portable HDD, and the disk is not in very good condition after some years of heavy use. At any rate, I was able to still copy over 99.99% of my files, so coming up... the actual scripts I used.

Hope they help somebody, as it did me.

Till then!