SMTP & Email Alerts
Send automated email notifications when entity values change.
Overview
ControlBird's SMTP & Email Alerts feature delivers automated email notifications whenever an entity's value changes. It uses the same controller / endpoint / mapper configuration model as the device-integration protocols: a controller manages the service, an endpoint describes the SMTP server connection, and one or more mappers watch specific entities and send mail when their value changes. Encryption is supported through STARTTLS and implicit TLS, message content is template-driven, and per-mapper cooldowns provide rate limiting to prevent email floods.
Use this feature to alert operators about critical conditions, notify teams about system events, or produce change reports, all without writing any code. An endpoint can authenticate with a stored password, or by connecting a Google or Microsoft 365 account instead (see below). An optional OAuth attribution mode can also send each message from the email address of the signed-in user who made the change, which is useful for audit trails. SMTP is a write-only protocol: ControlBird only sends mail, it never reads a mailbox, and that holds for a connected account too: it is authorized to send, not to read the mailbox.
Prefer a guided tutorial?
New to this? Follow the SMTP Email Alerts walkthrough for a step-by-step tour, then come back here for the full reference.
Key Concepts
- Mapper pattern. When a watched entity's value changes, the watching
SmtpMappertriggers the controller to send an email. This is the same controller / endpoint / mapper layering used by the protocol integrations. - Three entity types. A
SmtpClientControllerruns the service, aSmtpClientEndpointdefines the server connection and sender identity, and eachSmtpMapperbinds one watched entity to a set of recipients and templates. - Template variables. Subject and body templates support
{{value}}(current entity value),{{entity_name}}(the watched entity's path), and{{timestamp}}(ISO 8601 time). - Rate limiting. A configurable cooldown sets the minimum time between emails for a single mapper.
- Account authentication. Setting an endpoint's
AuthTypetoOAuth2AuthCodeconnects a Google or Microsoft 365 account and sends through it instead of a stored password. This authenticates the SMTP connection itself, which is a different mechanism from OAuth attribution below: attribution only changes the From address of an outgoing message. - OAuth attribution. With
UseWriterEmailIfAvailableenabled, a change made by an OpenID Connect user can be sent from that user'sOAuthEmailaddress instead of the endpoint's default sender. - Header-injection safety. Subject lines are sanitized so template content can never inject extra mail headers.
Architecture
An email-alert integration is assembled from three layers of entities. The controller manages the service lifecycle, the endpoint carries the SMTP server connection and sender identity, and each mapper binds a watched entity to its recipients and message templates.
| Layer | Entity | Role |
|---|---|---|
| Controller | SmtpClientController | Manages the overall SMTP email notification service |
| Endpoint | SmtpClientEndpoint | SMTP server connection, credentials, and sender identity |
| Mapper | SmtpMapper | Watches an entity and sends templated mail when its value changes |
Entity Reference
SmtpClientController
Manages the overall SMTP email notification service, following the same controller layer used by the device-integration protocols.
| Field | Type | Purpose |
|---|---|---|
Name | String | Display name for the controller (e.g. EmailNotifications) |
Description | String | Optional documentation describing this controller's purpose |
Disabled | Bool | Set to true to disable all SMTP functionality without removing configuration |
EndpointConnectionMode | Choice | Controls how the endpoint connection is established across the deployment |
SmtpClientEndpoint
Defines the SMTP server connection details, credentials, and sender identity.
| Field | Type | Purpose |
|---|---|---|
Host | String | SMTP server hostname (e.g. smtp.gmail.com, smtp.office365.com) |
Port | Int | SMTP server port; typically 587 for STARTTLS, 465 for implicit TLS, 25 for plaintext |
Security | Choice | Encryption mode: None (plaintext), StartTls (default), or ImplicitTls |
Username | String | SMTP authentication username (optional if the server allows anonymous) |
Password | String | SMTP authentication password, or an app-specific password for services like Gmail; used only when AuthType is Password |
AuthType | Choice | How the endpoint authenticates: Password (default), OAuth2AuthCode to connect a Google or Microsoft 365 account, or None for a relay that needs no authentication |
OAuthClientID | String | Client ID from your own OAuth app registration; required when AuthType is OAuth2AuthCode |
OAuthClientSecret | String | Client secret from the same OAuth app registration |
OAuthAuthorizationEndpoint | String | The provider's authorization URL; filled in automatically by the Gmail and Microsoft 365 presets in the Add Integration wizard |
OAuthTokenEndpoint | String | The provider's token URL; filled in automatically by the presets |
OAuthScopes | String | Space-separated OAuth scopes requested during authorization; filled in automatically by the presets |
FromAddress | String | Default sender email address used in all notifications |
FromName | String | Display name for the sender (optional; creates Name <email> format) |
TimeoutMs | Int | SMTP connection timeout in milliseconds (default 30000) |
UseWriterEmailIfAvailable | Bool | When true, sends from the OAuth user's email address instead of FromAddress if the writer has AuthMethod = OpenID Connect |
OAuthLastError | String | Runtime field: the error from the last failed authorization or token refresh, when AuthType is OAuth2AuthCode (read-only) |
SmtpMapper
Watches an entity's value for changes and triggers SMTP notifications with customizable templates and recipients. The last three fields are runtime statistics updated after every send attempt and are read-only.
| Field | Type | Purpose |
|---|---|---|
TargetEntity | EntityReference | The entity whose value changes trigger email notifications |
TargetField | String | The field on TargetEntity to watch. Defaults to Data, which is correct for most device points and calculated values. For an Alarm, set this to CurrentSeverity, which changes on activation, escalation, and clear (a cleared alarm reports Normal); the fastest way to wire this up is the Add email notification action in the Active Alarms app, which sets both fields for you |
Recipients | String | Comma-separated list of recipient email addresses (at least one required) |
CcRecipients | String | Comma-separated list of CC recipients (optional) |
SubjectTemplate | String | Email subject line with template variables {{value}}, {{entity_name}}, {{timestamp}} |
BodyTemplate | String | Email body content with the same template variables; supports plain text or HTML |
ContentFormat | Choice | Email body format: Text (plain text) or Html (HTML with text fallback) |
CooldownMs | Int | Rate limiting: minimum milliseconds between emails for this mapper (0 = no rate limit) |
TriggerMode | Choice | Trigger condition: OnChange (only when the value differs) or OnAnyWrite (on any write) |
EmailsSent | Int | Runtime counter: total emails successfully sent by this mapper (read-only) |
LastSentAt | Timestamp | Runtime field: when the last email was successfully sent (read-only) |
LastError | String | Runtime field: error message from the last failed send attempt (read-only) |
Template Variables
Subject and body templates accept the following placeholders, expanded at send time:
| Variable | Resolves to |
|---|---|
{{value}} | The current value of the watched entity |
{{entity_name}} | The watched entity's path |
{{timestamp}} | The current time in ISO 8601 format |
Content normalization
Subject lines have newlines removed to prevent header injection, and body line endings are normalized. When ContentFormat is Html, a plain-text alternative is generated automatically, so clients that cannot render HTML still receive readable text.
SMTP Server Configuration
Set the endpoint Host, Port, and Security to match your mail provider. The three security modes are None (plaintext), StartTls (upgrade the connection after connecting), and ImplicitTls (TLS from the first byte). Authentication is set separately with AuthType: Password for a stored username and password, OAuth2AuthCode to connect a Google or Microsoft 365 account, or None for a relay that needs no authentication.
Connecting a Google or Microsoft 365 account
In the Add Integration wizard, picking Gmail or Microsoft 365 as the provider fills in the server address, port, security mode, and that provider's OAuth endpoints and scope. It cannot fill in an OAuth app of your own, and that step is not optional: Google and Microsoft both require every application to register itself before it can request access to an account, so you register your own app and paste its client ID and secret into the wizard. Expect a real, multi-step setup rather than a single click:
- Gmail: in Google Cloud Console, create a project, enable the Gmail API, configure the OAuth consent screen with the
gmail.sendscope, and create an OAuth client ID for a web application. - Microsoft 365: in the Microsoft Entra admin center, register an app, grant it the
SMTP.Sendandoffline_accessAPI permissions, and create a client secret.
Register the exact redirect URI
Both app registrations ask for a redirect (callback) URI. Register this one, character for character:
<your platform origin>/api/oauth/endpoint/callbackYour platform origin is the scheme and host you use to reach ControlBird, with no path after it, for example https://plant1.example.com. Getting this wrong is the hardest failure in this flow to diagnose: a mismatch fails at Google or Microsoft, before any ControlBird screen appears, so the error and its wording come entirely from the provider.
Back in ControlBird, paste the client ID and client secret into the matching provider group, save the endpoint, then use its Connect with OAuth control. A popup carries you through the provider's sign-in and consent screens; when it closes, the endpoint reports itself connected. Tokens refresh automatically before they expire; if a refresh fails, the control changes to Reconnect and the failure is recorded rather than left silent (see OAuthLastError above and LastError on each mapper).
Gmail rewrites the From address
Gmail sends as the authenticated account, not whatever FromAddress says. If FromAddress does not match the connected account, or one of its verified send-as aliases, Gmail substitutes the authenticated address instead of rejecting the message, and it does this silently. Set FromAddress to the mailbox you connected, or a message that looks fine in ControlBird will arrive from an address you did not choose.
A factory restore disconnects every mailbox
A factory restore recreates the node's data directory, and the stored authorization for every connected mailbox goes with it. Each one needs to be reconnected afterward, the same as any other endpoint configured before the restore. This is easy to miss until an incident, when notifications that used to fire silently do not.
Password authentication and other providers
AuthType = Password still works for any SMTP server that accepts it, including Amazon SES and self-hosted mail relays, and remains the default for new endpoints.
# Amazon SES
Host = email-smtp.us-east-1.amazonaws.com
Port = 465
Security = ImplicitTls
AuthType = Password
# Gmail or Microsoft 365, password authentication (legacy)
Host = smtp.gmail.com # or smtp.office365.com
Port = 587
Security = StartTls
AuthType = Password
Username = [email protected]
Password = your-app-password
FromAddress = [email protected]Password authentication is on borrowed time
Microsoft has begun rejecting Basic-authentication SMTP submissions for Exchange Online, turns it off by default for existing tenants at the end of 2026, and removes it entirely in 2027. Google Workspace withdrew less-secure-app sign-in in May 2025. For either provider, a username-and-password mail connection will stop working eventually; connect the account with OAuth where you can.
Gmail requires App Passwords
When 2-factor authentication is enabled on a Google account, regular passwords fail with SMTP authentication errors. Generate an App Password in your Google Account settings and use it as the endpoint Password. This only applies to AuthType = Password; a connected account (OAuth2AuthCode) does not use a password at all.
Message Templates
Mapper templates support plain text or HTML. The examples below use the template variables to embed the live value, the entity identifier, and the time of the change.
# Simple plain-text alert
Subject = Alert: {{entity_name}} changed
Body = Value changed to {{value}} at {{timestamp}}# HTML report (ContentFormat = Html)
Subject = ControlBird: {{entity_name}} Update
Body =
<h2>Value Update</h2>
<p>Entity: <strong>{{entity_name}}</strong></p>
<p>New Value: <code>{{value}}</code></p>
<p><small>{{timestamp}}</small></p>Recipients
Recipients and CcRecipients are comma-separated lists; surrounding whitespace is trimmed. At least one valid recipient is required for the mapper to send.
Recipients = [email protected], [email protected]Rate Limiting
Set CooldownMs to throttle a noisy mapper. Any email that would fall inside the cooldown window since the last send is skipped.
# Send at most one email per minute from this mapper
CooldownMs = 60000 # any change within 60s of the last send is skippedCooldown scope
Rate limiting applies per mapper only: multiple mappers send independently and do not respect each other's cooldowns. The cooldown resets when the service restarts.
OAuth Email Attribution
When UseWriterEmailIfAvailable is enabled on the endpoint, an email triggered by a user action can be sent from that user's address, which is useful for audit trails. All three of the following conditions must hold:
UseWriterEmailIfAvailable = trueon the endpoint.- The writer's
AuthMethodisOpenID Connect. - The writer has a non-empty
OAuthEmailfield.
From-address fallback
If the OAuth email lookup fails or the feature is disabled (for example a Native or LDAP user makes the change, or the OpenID Connect user has no OAuthEmail), the endpoint's FromAddress is used instead, and it must be set. Alarm and other service-driven notifications are authored by a service, not an OpenID Connect user, so the writer-email path never applies to them and FromAddress is required. If it is empty, the mapper reports a FromAddress required error instead of silently dropping the email.
Common Patterns
- Critical alerts. Watch an alarm or status entity with
TriggerMode = OnChangeand a short plain-text template so operators get immediate, low-noise notifications. For an alarm, pointTargetEntityat the alarm and setTargetFieldtoCurrentSeverity(written on activation, escalation, and clear alike). The quickest way to set this up is the Add email notification action on any alarm in the Active Alarms app, which creates the wiring for you. - HTML status reports. Use
ContentFormat = Htmlwith a richly formatted body; the automatic plain-text alternative covers clients that cannot render HTML. - Throttled high-frequency signals. Pair a frequently changing source with
CooldownMsto cap the email rate while still surfacing the latest value. - Audited operator actions. Enable
UseWriterEmailIfAvailableso change notifications carry the originating OpenID Connect user's address. - Monitoring the service. Watch
EmailsSent,LastSentAt, andLastErroron each mapper to confirm delivery and catch failures.
Troubleshooting & Limitations
- Gmail authentication errors. Use an App Password when 2-factor authentication is enabled; a normal password will be rejected.
- Controller will not initialize.
FromAddressmust be a valid email format; an invalid value prevents controller initialization. - Mapper never sends. The
Recipientsfield cannot be empty: at least one comma-separated address is mandatory. Malformed addresses cause the entire send to fail. - Attribution From not applied. OAuth attribution only works for
OpenID Connectusers; Native and LDAP users cannot use it. If the conditions are not met the endpoint'sFromAddressis used instead, so it must be set, and alarm notifications in particular always fall through to it. - Account connection fails or stops sending. Check
OAuthLastErroron the endpoint andLastErroron the affected mapper: OAuth failures (a missing token, an expired refresh token, a rejected authentication attempt) are recorded there rather than dropped silently. A stuck connection usually means the account's consent was revoked or the client secret changed; reconnect the endpoint to fix it. - Header injection. Newlines in subject templates would allow header injection, so the service strips them from subjects automatically.
- No retries. The service does not retry failed sends. A failure is recorded in
LastErrorand the send is abandoned. - Write-only protocol. SMTP only sends mail. There is no inbound or receive path by design.
- Inspecting traffic. The service logs SMTP traffic (MAIL FROM, TO, SUBJECT, and error responses) for diagnosis.