Overview
SIMATIC WinCC Runtime Professional (TIA Portal V14 SP1 and later) supports two primary scripting paths: ANSI-C scripts compiled to the runtime and VBScript (VB Script) used inside graphic screens and global actions. Engineers frequently need to forward alarms to email recipients, and the TIA Portal does not expose a native SMTP client in the C script API. The supported Siemens path is documented in the manufacturer's FAQ Email Notification with SIMATIC WinCC Runtime Professional (ID 58983189), which describes a VBScript-based SMTP flow built on the CDO.Message COM object.
This article documents the complementary pattern: wrap SMTP (or any custom notification) inside a C# Windows Forms User Control, expose it as a method on the control, embed an instance of the control on a WinCC screen, and trigger the method from a C script by writing a "dummy" property. This pattern solves two field-proven problems in TIA Portal V14 SP1 Update 7:
- Race conditions in alarm-driven VB scripts that drop messages when alarms fire in bursts.
- The absence of a native C-script SMTP API, by delegating the network I/O to managed .NET code loaded into the WinCC Runtime process.
Architecture Options for Alarm Email
Three architectures are commonly deployed in WinCC Professional installations. Pick the one that matches your alarm rate, network reachability, and acceptable dependency on PLC scan logic.
| Architecture | Mechanism | Pros | Cons |
|---|---|---|---|
| VB Script + CDO.Message on screen | VBScript on a screen calls CDO.Message directly. |
No DLL; uses documented Siemens FAQ path. | Tied to a screen; loses messages if screen is not active; subject to VBScript runtime limits. |
| PLC buffer + WinCC scheduler | C script writes alarm text to a PLC tag; tag triggers VB script that drains a buffer. | Survives screen changes; can be rate-limited by a timer. | PLC scan latency, two-buffer pointer management, dropped alarms if buffer fill exceeds timer window. |
| C# User Control + C script trigger | A System.Windows.Forms.UserControl exposes a SendEmail(string text) method; a C script sets a public property on the control. |
Survives screen change (the control remains loaded), full .NET SMTP access, deterministic method invocation, easy to unit-test. | Requires a separate C# project; must be deployed with WinCC Runtime; version-pinned to the TIA Portal .NET target. |
Prerequisites
- TIA Portal V14 SP1 Update 7 or later installed on the engineering station.
- SIMATIC WinCC Runtime Professional licensed and installed on the target PC or panel.
- Microsoft Visual Studio 2013 / 2015 / 2017 (matching the TIA Portal .NET target) to build the C# class library and Windows Forms User Control. Reference Microsoft's How to create a user control (Windows Forms .NET) walk-through for the UserControl template.
- An SMTP relay reachable from the WinCC Runtime host (corporate Exchange, smarthost, or local relay). Note credentials, port (25 / 465 / 587), and TLS requirement.
- Administrative access on the runtime machine to deploy the compiled DLL into the WinCC project bin folder.
- PLC tags (HMI tags in WinCC) declared for the alarm text, buffer index, and "trigger" flag.
Step 1: Build the C# User Control with a SendEmail Method
Create a new Class Library (.NET Framework) project in Visual Studio. Target the same .NET Framework version as your TIA Portal installation (4.5.2 for V14 SP1, 4.6.x for V15/V15.1, 4.7.2 for V16/V17, 4.8 for V18). Add a UserControl item.
- In Visual Studio, right-click the project, Add > New Item > User Control (Windows Forms). Name it
MailNotifierControl. - Add a public string property
DummyTrigger. The C script writes to it; the property setter invokes the send routine. - Implement SMTP using
System.Net.Mail.SmtpClient. Do not block the WinCC UI thread; offload the send toSystem.Threading.Tasks.Task.Runand return immediately.
// MailNotifierControl.cs - compile into Class Library (.NET Framework)
using System;
using System.ComponentModel;
using System.Drawing;
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WinCCMailLib
{
public partial class MailNotifierControl : UserControl
{
// SMTP configuration - move to app.config in production
private const string SmtpHost = "smtp.corp.local";
private const int SmtpPort = 587;
private const string SmtpUser = "[email protected]";
private const string SmtpPass = "P@ssw0rd!";
private const string FromAddr = "[email protected]";
private const string ToAddr = "[email protected]";
public MailNotifierControl()
{
InitializeComponent();
}
// Public property the WinCC C script writes to.
// Any set operation triggers SendEmail_implementation with
// the current text held in BufferText.
private string _dummy = string.Empty;
public string DummyTrigger
{
get { return _dummy; }
set
{
_dummy = value ?? string.Empty;
if (_dummy.Length > 0)
{
SendEmail_implementation(BufferText);
}
}
}
// Public text buffer the C script can populate before triggering.
private string _buffer = string.Empty;
public string BufferText
{
get { return _buffer; }
set { _buffer = value ?? string.Empty; }
}
// Public method exposed to the runtime.
public void SendEmail_implementation(string text)
{
// Marshal network I/O off the UI thread so WinCC remains responsive.
Task.Run(() =>
{
try
{
using (var msg = new MailMessage())
{
msg.From = new MailAddress(FromAddr, "WinCC Alarm");
msg.To.Add(ToAddr);
msg.Subject = "[WinCC Alarm] " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
msg.Body = string.IsNullOrEmpty(text)
? "An alarm has occurred."
: text;
msg.IsBodyHtml = false;
using (var client = new SmtpClient(SmtpHost, SmtpPort))
{
client.EnableSsl = true;
client.UseDefaultCredentials = false;
client.Credentials = new NetworkCredential(SmtpUser, SmtpPass);
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.Send(msg);
}
}
}
catch (Exception ex)
{
// Log to WinCC diagnostics path so the field engineer can pick it up.
System.IO.File.AppendAllText(
@"C:\Siemens\Automation\WinCC_RT_Professional\MailLog.txt",
DateTime.Now + " " + ex.ToString() + Environment.NewLine);
}
});
}
}
}
The DummyTrigger / BufferText pair is the pattern recommended by the field community when you cannot directly invoke a method on a UserControl from a C script: you set a property, the property setter dispatches the call. (See the source thread's schufri example for the original pattern that this article extends with the SMTP payload.)
Step 2: Compile and Deploy the DLL
- In Visual Studio set the project to Release | AnyCPU and build. The output DLL is, for example,
WinCCMailLib.dll. - Copy
WinCCMailLib.dllinto the WinCC Runtime project folder on the target, typically<Project>\WinCCRT\<PCName>\bin\(or the corresponding path inside a TIA Portal archive after Compile > Software (PC)). - Confirm that the .NET Framework version of the DLL matches the runtime. Mixed-version loads are a common cause of
FileNotFoundExceptionat WinCC startup. - Register the control in the WinCC screen designer by opening the Toolbox > Choose Items > .NET Framework Components dialog and browsing to
WinCCMailLib.dll. TheMailNotifierControlshould now appear in the toolbox.
gacutil -i or copy it into the WinCC bin directory. Unsigned assemblies still load from bin\ in most panels.Step 3: Place the User Control on a WinCC Screen
- In the TIA Portal project tree, expand HMI > Screens and open (or create) a permanent screen, e.g.
Screen_100_AlarmMail. WinCC Runtime only invokes a screen's loaded controls while the screen is the active screen; a permanent background screen is therefore required. - Drag
MailNotifierControlfrom the toolbox onto the screen. Resize it to a minimal footprint, e.g. 1 x 1 pixel, positioned at(0,0)or off-canvas. - Open the control's properties and assign a stable name, for example
MailNotifier1. This name is what the C script will use to access the control's properties. - Verify the screen's Global Animation / Always-On-Top settings so the screen loads at runtime startup. Configure this in Runtime Settings > General > Start Screen and ensure the screen is set as the start screen or is opened by a global scheduler at boot.
Step 4: Write the C Script that Triggers the Control
WinCC C scripts have access to the active screen's GetObject and SetProp helpers. Use them to write the DummyTrigger and BufferText properties on the control. Trigger the C script from the Status Changed alarm event.
- Open HMI > Events > Alarms > (alarm of interest) > Status Changed.
- Add a C function, for example
NotifyMailOnAlarm. - Pass the alarm text via the
lpszPictureNameand standard alarm API.
// NotifyMailOnAlarm.c - WinCC C script attached to an alarm Status Changed event
#include "apdefap.h"
void NotifyMailOnAlarm(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName)
{
// Buffer 1: tag that holds the comma-joined alarm texts.
// BufferFull: tag that the PLC sets to TRUE when the buffer hits its capacity.
const char* TAG_BUFFER = "HMI_AlarmBuffer_1";
const char* TAG_BUFFER_FULL = "HMI_BufferFull_1";
const char* TAG_LAST_TEXT = "HMI_LastAlarmText";
// Grab the alarm text passed in via lpszObjectName (the alarm name) and any
// additional payload formatted by your alarm logging script.
const char* alarmText = lpszObjectName;
// Append to the HMI tag (your own concatenation function).
AppendToHmiTag(TAG_BUFFER, alarmText);
SetTagChar(TAG_LAST_TEXT, alarmText);
// If the PLC flagged the buffer as full, drain it by writing to the
// UserControl on Screen_100_AlarmMail.
if (GetTagBit(TAG_BUFFER_FULL))
{
const char* SCREEN_NAME = "Screen_100_AlarmMail";
const char* CONTROL_NAME = "MailNotifier1";
// Set the body text first.
SetPropChar(SCREEN_NAME, CONTROL_NAME, "BufferText", GetTagChar(TAG_BUFFER));
// Trigger the SendEmail call by writing a non-empty DummyTrigger.
SetPropChar(SCREEN_NAME, CONTROL_NAME, "DummyTrigger", "GO");
// Reset the HMI-side buffer tag; PLC handles pointer flip.
SetTagChar(TAG_BUFFER, "");
SetTagBit(TAG_BUFFER_FULL, 0);
}
else
{
// Optional: still send an immediate single-alarm notification for
// time-critical loops. Same mechanism.
const char* SCREEN_NAME = "Screen_100_AlarmMail";
const char* CONTROL_NAME = "MailNotifier1";
SetPropChar(SCREEN_NAME, CONTROL_NAME, "BufferText", alarmText);
SetPropChar(SCREEN_NAME, CONTROL_NAME, "DummyTrigger", "GO");
}
}
SetPropChar, GetTagBit, SetTagChar, and GetTagChar are part of the WinCC C API. Check the Siemens Industry Online Support portal for the exact C-script header set of your TIA Portal version. Names such as AppendToHmiTag above are user-defined; declare them in the project header.Step 5: Buffering Logic to Avoid Lost Alarms
The original PLC buffer approach in the source thread is fragile when alarms fire faster than the send window. A robust variant uses two server-side accumulator strings in the HMI tag database, swapped by the PLC when a "drain" tag is set.
| Tag | Direction | Type | Purpose |
|---|---|---|---|
HMI_AlarmBuffer_1 |
HMI <-> PLC | WString[512] | Active accumulator; receives concatenated alarm text. |
HMI_AlarmBuffer_2 |
HMI <-> PLC | WString[512] | Staging accumulator; ready to swap in. |
HMI_BufferFull_1 |
PLC <- HMI | Bool | PLC raises when buffer approaches a high-water mark. |
HMI_BufferActive |
HMI <- PLC | Bool | Indicates which buffer is currently active. |
HMI_DrainRequest |
PLC <- HMI | Bool | WinCC sets when an email send has been queued. |
HMI_DrainAck |
HMI <- PLC | Bool | PLC acknowledges that the buffer was cleared and swapped. |
On a high-rate alarm burst, the C script appends to the active buffer, sets HMI_BufferFull_1 when length > 380 chars, and the PLC swaps buffers and raises HMI_DrainAck. The C script reads the staged buffer only after HMI_DrainAck is true, eliminating the race condition where two C-script invocations both send at the same time.
Alternative: Direct SMTP from a VB Script (No DLL)
If you cannot ship a C# DLL with the project, follow the Siemens official FAQ 58983189 - Email Notification with SIMATIC WinCC Runtime Professional. The canonical implementation uses the CDO.Message COM object from a VBScript scheduled action:
' VBScript scheduled action - send queued alarms
Dim msg, cfg
Set msg = CreateObject("CDO.Message")
Set cfg = msg.Configuration
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2 ' SMTP
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "smtp.corp.local"
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 587
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = 1 ' basic
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/sendusername") = "[email protected]"
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/sendpassword") = "P@ssw0rd!"
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/smtpusessl") = True
cfg.Fields.Update
msg.From = "[email protected]"
msg.To = "[email protected]"
msg.Subject = "[WinCC Alarm] " & FormatDateTime(Now, 0)
msg.TextBody = SmartTags("HMI_AlarmBuffer_1")
msg.Send
This path is preferred for fleets where you cannot deploy a C# DLL. Combine it with the two-buffer swap in Step 5 to avoid lost alarms under burst conditions.
Verification
-
Unit test the DLL: open a WinForms test harness, instantiate
MailNotifierControl, setBufferTextto a known string, write any non-empty value toDummyTrigger, and confirm the SMTP relay logs the message. The C# code path is independent of WinCC, so failures here are pure .NET bugs. -
Live-Watch in WinCC: in the runtime, open the HMI tag list, force
HMI_AlarmBuffer_1to"TEST ALARM 1\nTEST ALARM 2", then watchMailLog.txtinC:\Siemens\Automation\WinCC_RT_Professional\for the SMTP send. A successful run leaves a 250 OK response from the relay. - Trigger a real alarm: force a high-priority alarm, observe the Status Changed event firing the C function, and verify the email arrives in the test mailbox within five seconds.
- Burst test: use a script to fire ten alarms within 200 ms. Confirm that the staging buffer flips exactly once and only one or two emails are sent, each containing all ten messages. No events should be missing from the email body.
- Screen-close test: with the runtime running, navigate away from the alarm-mail screen. Re-fire an alarm. The email should still arrive because the screen remains loaded by the runtime scheduler.
Troubleshooting Matrix
| Symptom | Likely Cause | Fix |
|---|---|---|
SetPropChar returns -1, no email sent. |
Screen is not loaded; MailNotifier1 does not exist on the active screen. |
Confirm the control's name in the screen's properties; ensure the screen is loaded as start screen or by a global action. |
Email never arrives, no exception in MailLog.txt. |
SMTP relay unreachable from the panel; firewall blocks port 587. | Telnet from the runtime host to smtp.corp.local 587. Add firewall rule; verify TLS cert chain. |
| Alarms lost during a 50-event burst. | Single-buffer pattern with a timer-driven drain; timer expires before buffer is full. | Switch to the two-buffer swap pattern in Step 5 and let the PLC raise the drain flag. |
Exception FileNotFoundException: WinCCMailLib at runtime start. |
Wrong .NET Framework version of the DLL; assembly shadow-copy issue. | Rebuild against the TIA Portal's .NET target version; copy DLL into bin\. |
Exception SmtpException: 5.7.0 Authentication required. |
SMTP relay requires authentication but client uses default credentials. | Set UseDefaultCredentials = false and supply NetworkCredential explicitly. |
C script NotifyMailOnAlarm never fires. |
Alarm's Status Changed event is not configured; the alarm is not in the active alarm class. | Open HMI Alarms > (alarm) > Events and bind Status Changed to the C function; check alarm class filter. |
Control property BufferText is empty in the email body. |
C script writes BufferText with the alarm name but the alarm text is delivered separately by WinCC. |
Pass the alarm message text via lpszObjectName or read the alarm text via the GetAlarmText API in C. |
Field-Proven Caveats
- WinCC Runtime Professional runs as a service on RT-panels. When using
System.Net.Mail, ensure the service account has network access; on locked-down Windows IoT images, the defaultSYSTEMaccount is fine for outbound SMTP but cannot access mapped drives for log file output. -
SetPropCharon a C# property is marshaled by the WinCC script engine; avoid calling it more than ~10 times per second or the runtime can stall on panel-class hardware. Use the buffer-and-trigger approach to keep the rate low. - Keep the UserControl in a screen that is loaded at startup. Hiding the screen is fine; closing it removes the control and breaks subsequent
SetPropcalls. - For high-availability sites, queue emails in a local SQLite store and drain from a background thread so a transient SMTP outage does not drop notifications. The
Task.Runwrapper above already non-blocks the UI; the SQLite layer can sit beside it. - On V14 SP1, hotfix Update 7 (or later) is required to correctly marshal
stringparameters into UserControl property setters. Earlier updates occasionally returnE_INVALIDARGsilently.
FAQ
How do I trigger a method on a C# UserControl from a WinCC Professional C script?
WinCC C scripts cannot call managed methods directly. Add a public property (for example DummyTrigger) on the UserControl whose setter invokes the method. Use SetPropChar(lpszPictureName, "ControlName", "DummyTrigger", "GO") from the C script; the property setter dispatches the call to SendEmail_implementation.
Can I send email from a WinCC Runtime Professional C script without a DLL?
Not directly. The C API does not expose SMTP. Use the VBScript path with CDO.Message documented in Siemens FAQ 58983189, or wrap SMTP in a C# User Control and trigger it from the C script as shown above.
Why are some alarms lost during high-rate bursts when using a PLC tag buffer?
Single-buffer schemes drop events when the drain timer expires before the buffer fills, or when two C-script invocations send at the same time. Implement a two-buffer swap with HMI_BufferActive, HMI_DrainRequest, and HMI_DrainAck handshakes so the PLC owns the buffer pointer and the C script only sends after the swap completes.
Which TIA Portal versions support the C# UserControl approach?
TIA Portal V14 SP1 Update 7 and later (V15, V15.1, V16, V17, V18, V19) all support it. Match the C# project's .NET Framework target to the TIA Portal version (4.5.2 for V14 SP1, 4.6.x for V15/V15.1, 4.7.2 for V16/V17, 4.8 for V18/V19) to avoid FileNotFoundException at runtime start.
Do I need to add the UserControl to a visible WinCC screen?
Yes. The control must be hosted on a screen that is loaded at runtime. You can place it off-canvas or at 1 x 1 pixel; visibility is not required, but screen load is. Set the host screen as the start screen or open it from a global action at boot.