Ignition Edge sends emails but no attachments

I have been able to get my ignition edge panel vision client send email with a button, however no attachments are sent. Is this system able to send .csv files as attachments?

Show your code! Please see Wiki - how to post code on this forum.

Welcome to the forum.

4 Likes
import system
import system.util
import datetime
from java.lang import String
from java.util import Date
from array import array

def email_historian_data_report():
    
    # Get a logger instance for debugging 
    logger = system.util.getLogger("Historian_Email_Report_CSV")
    logger.info("--- SCRIPT STARTING (CSV Attachment Test) ---")
    
    # --- CONFIGURATION: UPDATE THESE THREE LINES ---
    EMAIL_PROFILE = "Gateway Profile" # <-- Changed for publishing
    RECIPIENT_ADDRESS = "receiver@email.com.au" # <-- Changed for publishing
    SENDER_ADDRESS = "sender@email.com"      
    # --- End Configuration ---
    
    # --- Historian Tag Configuration (Add more tags as required) ---
    tag_paths_to_export = [
        "[edge]PLC/Controller:Global/LT101/Trend"
        # Add more tags here if needed
    ]
    # --- End Configuration ---

    try:
        # --- 1. Calculate Date Range (Previous Sunday to Now) ---
        now = system.date.now()
        
        startDate = system.date.midnight(now)
        while system.date.getDayOfWeek(startDate) != 1: 
            startDate = system.date.addDays(startDate, -1)
            
        if system.date.getDayOfWeek(now) == 1 and system.date.isSameDay(now, startDate):
            startDate = system.date.addWeeks(startDate, -1) 
            
        endDate = now
        
        start_date_str = system.date.format(startDate, "yyyy-MM-dd HH:mm:ss")
        end_date_str = system.date.format(endDate, "yyyy-MM-dd HH:mm:ss")
        logger.info("Querying history from {} to {}".format(start_date_str, end_date_str))

        if not tag_paths_to_export:
            system.gui.warningBox("No historized tags defined in the script for export.")
            return

        # --- 2. Query Tag History ---
        historical_data = system.tag.queryTagHistory(
            paths=tag_paths_to_export,
            startDate=startDate,
            endDate=endDate,
            intervalSeconds=120,          
            aggregationMode="LastValue",  
            columnFormat="Tall"           
        )
        
        row_count = historical_data.getRowCount()
        logger.info("Query returned {} rows of historical data.".format(row_count))

        # --- 3. Check Data and Convert ---
        if row_count <= 1: 
            logger.warn("No valid historical data found (row count <= 1). Aborting email send.")
            system.gui.warningBox("No historical data found. Email not sent.")
            return
            
        # Convert the dataset to a CSV string
        csv_string = system.dataset.toCSV(historical_data)

        # Convert Python string to Java Byte Array for attachment
        # This prevents the 'unicode' object has no attribute 'getBytes' error.
        java_byte_string = String(csv_string).getBytes("UTF-8")
        attachmentBytes = array('b', java_byte_string)

        logger.info("CSV attachment size: {} bytes".format(len(attachmentBytes)))

        # Generate file name dynamically
        file_name = "WeeklyHistorianExport_{}.csv".format(
            system.date.format(now, "yyyyMMdd_HHmmss")
        )

        # --- 4. Prepare and Send Email ---
        attachments = [
            {
                'data': attachmentBytes,
                'name': file_name,
                'contentType': 'text/csv'
            }
        ]
        
        email_subject = "Weekly Historian Report (CSV ATTACH): {} to {}".format(
             system.date.format(startDate, "yyyy-MM-dd"),
             system.date.format(endDate, "yyyy-MM-dd HH:mm:ss")
        )
        
        email_body = "Please find the attached CSV file with the historical data from {} until {}.".format(
             system.date.format(startDate, "yyyy-MM-dd HH:mm"),
             system.date.format(endDate, "yyyy-MM-dd HH:mm")
        )

        system.net.sendEmail(
            smtpProfile=EMAIL_PROFILE,
            to=[RECIPIENT_ADDRESS],
            fromAddr=SENDER_ADDRESS,
            subject=email_subject,
            body=email_body,
            html=False, # Body is plain text for this testing purposes
            attachments=attachments 
        )
        
        logger.info("--- SCRIPT ENDED SUCCESSFULLY ---")
        system.gui.messageBox("Success! Historical data report has been sent to {}.".format(RECIPIENT_ADDRESS))

    except Exception as e:
        logger.error("Error in email_historian_data_report: {}".format(e))
        system.gui.errorBox("Error sending historical data email: {}".format(e) + "\nCheck Gateway logs for details on the failure.")

# Call the main function when the script runs
email_historian_data_report()

It seems like the keyword args are attachmentNames and attachmentData per the docs for system.net.sendEmail. Looking back in history it seems it has been that way for some time (at least back to 7.9)--is there a reason you're using attachments instead and where did you source that structure?

2 Likes

It was recommended to me by a coworker. So based on that, if I changed it to attachmentNames=attachments (where attachments is defined earlier), then I’d have better luck?

Take a look at the docs link I posted above.

1 Like

Also, I'll say it for the record, though it's likely to fall on deaf ears:

Don't use LLMs to create Ignition code blindly.

If you don't already know what you're doing, an LLM will happily give you a mountain of terrible code.

I'm not going to bother listing them all, but besides the parameter name hallucination there's a large number of problems with the single code snippet you've posted.

7 Likes

I hear you Paul! I am learning, and when trying to go beyond what the user manual outlines I have reached out for some help. Trying to decipher what it is doing and then adjusting from there.

Similar setup here - nothing against you personally, it's just that each time I work through a "person got a mountain of code from an LLM" post and try to guide them through all the things it did wrong makes me feel like Sisyphus.

Top level notes:

  • You should only rarely need to import anything in small Ignition scripts. Any imports you do use, you should be judicious about.
    • You should never import system or anything from system.
    • You should never use Jython's datetime library.
    • You should (almost) never have to import core Java classes like Date and String
  • Defining an email_historian_data_report() function only to call it in the same file is totally pointless. If you're going to define this as a function, it should be in the project library, and it should accept useful parameters (potentially with default values).
  • while loops are rarer than imports and should also almost never be used.
  • system.gui.warningBox is a Vision only function that precludes this script from working in Perspective or being called from anywhere else in Ignition.
  • Logging statements should be called with infof variants to do string formatting lazily, for the sake of performance.
  • There's no need to import array or coerce the Jython string into a Java string in order to pass it to the attachment parameter
  • A try/catch Exception around the whole block is only going to cause you grief troubleshooting, as it won't catch Java exceptions and it throws away the stacktrace, the most useful element for troubleshooting.
9 Likes

Alright team, I’ve headed back to the user manual and just tried using the basic send email with attachment function, using an smtpProfile, and trying to attach a CSV.
Now, no email sends from that email profile which i know is capable of sending emails. Any Ideas of what paths i need to be chasing?

Code

filePath = system.file.openFile()
if filePath != None:

This gets the filename without the C:\folder stuff

fileName = filePath.split("\")[-1]
fileData = system.file.readFileAsBytes(filePath)
smtpProfile="ECS Email Profile Name"
subject = "Here is the file you requested"
body = "Hello, this is an email."
recipients = ["mXXXX@ecs.com.au"]
system.net.sendEmail(smtpProfile, subject, body, False, recipients, [fileName], [fileData])

The error i Receive is this (short)

Traceback (most recent call last):
File "event:actionPerformed", line 10, in
java.lang.ClassCastException: java.lang.ClassCastException: Cannot coerce type 'org.python.core.PyList' to number type 'java.lang.Boolean'

caused by ClassCastException: Cannot coerce type 'org.python.core.PyList' to number type 'java.lang.Boolean'

Ignition v8.3.0 (b2025091510)
Java: Azul Systems, Inc. 17.0.16

(long)

Traceback (most recent call last):
File "event:actionPerformed", line 10, in
at com.inductiveautomation.ignition.common.TypeUtilities.coerceNumber(TypeUtilities.java:1463)
at com.inductiveautomation.ignition.common.TypeUtilities.coerce(TypeUtilities.java:1601)
at com.inductiveautomation.ignition.common.script.builtin.PyArgumentMap.coerce(PyArgumentMap.java:130)
at com.inductiveautomation.ignition.common.script.builtin.PyArgumentMap.interpretPyArgs(PyArgumentMap.java:82)
at com.inductiveautomation.ignition.common.script.builtin.PyArgumentMap.interpretPyArgs(PyArgumentMap.java:40)
at com.inductiveautomation.ignition.common.script.builtin.AbstractNetUtilities.sendEmail(AbstractNetUtilities.java:145)
at jdk.internal.reflect.GeneratedMethodAccessor89.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
java.lang.ClassCastException: java.lang.ClassCastException: Cannot coerce type 'org.python.core.PyList' to number type 'java.lang.Boolean'

at org.python.core.Py.JavaError(Py.java:545)
at org.python.core.Py.JavaError(Py.java:536)
at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:192)
at com.inductiveautomation.ignition.common.script.ScriptManager$ReflectedInstanceFunction.__call__(ScriptManager.java:563)
at org.python.core.PyObject.__call__(PyObject.java:433)
at org.python.core.PyObject.__call__(PyObject.java:437)
at org.python.pycode._pyx86.f$0(<event:actionPerformed>:10)
at org.python.pycode._pyx86.call_function(<event:actionPerformed>)
at org.python.core.PyTableCode.call(PyTableCode.java:173)
at org.python.core.PyCode.call(PyCode.java:18)
at org.python.core.Py.runCode(Py.java:1703)
at com.inductiveautomation.ignition.common.script.ScriptManager.runCode(ScriptManager.java:813)
at com.inductiveautomation.factorypmi.application.binding.action.ActionAdapter.runActions(ActionAdapter.java:212)
at com.inductiveautomation.factorypmi.application.binding.action.ActionAdapter.invoke(ActionAdapter.java:303)
at com.inductiveautomation.factorypmi.application.binding.action.RelayInvocationHandler.invoke(RelayInvocationHandler.java:57)
at jdk.proxy2/jdk.proxy2.$Proxy77.actionPerformed(Unknown Source)
at java.desktop/javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
at java.desktop/javax.swing.DefaultButtonModel.setPressed(Unknown Source)
at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(Unknown Source)
at java.desktop/java.awt.Component.processMouseEvent(Unknown Source)
at java.desktop/javax.swing.JComponent.processMouseEvent(Unknown Source)
at java.desktop/java.awt.Component.processEvent(Unknown Source)
at java.desktop/java.awt.Container.processEvent(Unknown Source)
at java.desktop/java.awt.Component.dispatchEventImpl(Unknown Source)
at java.desktop/java.awt.Container.dispatchEventImpl(Unknown Source)
at java.desktop/java.awt.Component.dispatchEvent(Unknown Source)
at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
at java.desktop/java.awt.Container.dispatchEventImpl(Unknown Source)
at java.desktop/java.awt.Window.dispatchEventImpl(Unknown Source)
at java.desktop/java.awt.Component.dispatchEvent(Unknown Source)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.desktop/java.awt.EventQueue$4.run(Unknown Source)
at java.desktop/java.awt.EventQueue$4.run(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
at java.desktop/java.awt.EventQueue$5.run(Unknown Source)
at java.desktop/java.awt.EventQueue$5.run(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
at java.desktop/java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.desktop/java.awt.EventDispatchThread.run(Unknown Source)

Caused by: java.lang.ClassCastException: Cannot coerce type 'org.python.core.PyList' to number type 'java.lang.Boolean'
at com.inductiveautomation.ignition.common.TypeUtilities.coerceNumber(TypeUtilities.java:1463)
at com.inductiveautomation.ignition.common.TypeUtilities.coerce(TypeUtilities.java:1601)
at com.inductiveautomation.ignition.common.script.builtin.PyArgumentMap.coerce(PyArgumentMap.java:130)
at com.inductiveautomation.ignition.common.script.builtin.PyArgumentMap.interpretPyArgs(PyArgumentMap.java:82)
at com.inductiveautomation.ignition.common.script.builtin.PyArgumentMap.interpretPyArgs(PyArgumentMap.java:40)
at com.inductiveautomation.ignition.common.script.builtin.AbstractNetUtilities.sendEmail(AbstractNetUtilities.java:145)
at jdk.internal.reflect.GeneratedMethodAccessor89.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.python.core.PyReflectedFunction.call(PyReflectedFunction.java:190)
... 48 more

Ignition v8.3.0 (b2025091510)
Java: Azul Systems, Inc. 17.0.16

Why are you enclosing fileName and fileData in [ ] ?

Edit: hmm nevermind, the user manual suggests this

None of the examples use this, they all use 0 or 1 instead of True/False.

system.net.sendEmail(smtp=smtpProfile, fromAddr=someAddress, subject=subject, body=body, html=0, to=recipients, attachmentNames=fileName, attachmentData=fileData)

You are missing the fromAddr, definitely should be using keyword arguments here for ease of troubleshooting.

This works for me, albeit I didn’t test on Edge;

filePath = system.file.openFile()
if filePath != None:
   # This gets the filename without the C:\folder stuff
   fileName = filePath.split("\\")[-1]
   fileData = system.file.readFileAsBytes(filePath)
   smtp_server = "test"
   sender = "test"
   subject_mail = "Here is the file you requested"
   body_mail = "Hello, this is an email."
   recipients = ["test"]
   
   system.net.sendEmail(
   	smtpProfile = smtp_server, fromAddr = sender, 
   	subject = subject_mail, body = body_mail, html = 0, 
   	to = recipients, attachmentNames = [fileName], 
   	attachmentData = [fileData])

image

1 Like