Email Notification Service
The Email Notification Service was added to the platform in v5.20.
The Email Notification Service sends automated email alerts when Cinchy data syncs, event listeners, or automations fail, succeed, or run longer than expected. When any of these artifacts hit a notable state, the affected team receives a branded email with the artifact name, execution details, and error summary so they can investigate immediately instead of discovering the failure hours later.
The service covers three artifact types:
- Batch syncs — scheduled data imports/exports.
- Event listeners — real-time listener-triggered syncs.
- Automations — multi-step Cinchy automation runs.
How it works
Batch Syncs and Event Syncs notify the service by webhook; automations are detected by background monitors. Every notification is persisted to a durable outbox before sending, so no email is lost even if the service restarts mid-send.
Access and permissions
By default, the Email Notification Service tables and the global kill switch are managed by the Cinchy Administrators User Group. The service itself runs as a dedicated notification@cinchy.com user with its own integrated client, so end users do not need direct access to send emails — they only need entitlements to manage which notifications are configured.
Administrators can delegate management of notification rules and branding to non-admin users or groups by granting per-table entitlements. For example, the data engineering team can own Notification Config rows for their syncs without needing platform admin rights.
| Table / Object | Access required for | Recommended entitlement |
|---|---|---|
[Cinchy].[Notification Config] | Creating per-artifact notification rules (recipients, trigger types, cooldowns, priority) | View + Edit |
[Cinchy].[Notification Branding] | Customizing logo, color, and company name on outgoing emails | View + Edit |
[Cinchy].[Notification Outbox] | Auditing sent/failed emails and troubleshooting delivery | View |
[Cinchy].[System Properties] row Notifications Enabled | Toggling the global kill switch | Edit (Administrators only) |
To grant access, open the table's Design Controls > Entitlements view in Cinchy and add the user or group with the appropriate permission level. The global kill switch should remain under Administrators only — per-row Send Email on the Notification Config table is the right place to delegate per-artifact control.
Prerequisites
- Cinchy v5.20+ is required.
- Kubernetes deployment is recommended. The service runs as a standalone container alongside the rest of the Cinchy platform.
- A reachable email provider (SMTP server, AWS SES, Azure Communication Services, or a Microsoft 365 tenant for Microsoft Graph).
Enabling the service
The service is disabled by default. How you enable it depends on your deployment platform:
- Kubernetes: Set
"notification_enabled": "true"interraform_config.notificationof yourdeployment.json. - IIS: Deploy the Notification service as an IIS sub-application under your Cinchy site. The service is opt-in by deployment — no additional flag is required in
appsettings.json.
In addition to the deployment flag, a System Property acts as the platform-level kill switch. In [Cinchy].[System Properties]:
| System Property | Purpose |
|---|---|
Notifications Enabled | Global kill switch. Set to true to enable. When false (or not set), the service sends nothing regardless of per-artifact configuration. |
There are two separate switches:
Notifications Enabled(System Property) — the global kill switch. Whenfalse(or not set), the service sends nothing, regardless of any per-artifact configuration. Set totrueto allow emails to send.Send Email(column on each[Cinchy].[Notification Config]row) — the per-artifact toggle, defaultfalse. A config row only generates emails when itsSend Emailis on and the global switch istrue.
Trigger types
Each row in the [Cinchy].[Notification Config] table gates which event types generate emails. There are three primary trigger types:
| Trigger Type | Default | Description | Wired for |
|---|---|---|---|
| Failure | Enabled | Sync, listener, or automation ended in a failure state | batch syncs, listeners, automations |
| Success | Disabled | Sync or automation completed successfully | batch syncs, automations |
| LongRunning | Disabled | Sync or automation has been in Running state longer than the configured threshold | batch syncs, automations |
The trigger types that apply to each artifact type are summarized below.
| Config row type | Notify On Failure | Notify On Success | Notify On Long Running |
|---|---|---|---|
| Data Sync Config | Yes | Yes | Yes |
| Listener Config | Yes | No | No |
| Automation | Yes | Yes | Yes |
For listener config rows, only Notify On Failure is wired end-to-end. Notify On Success has no upstream emitter today, and Notify On Long Running is intentionally out of scope (event-driven listeners are not batch).
A [Cinchy].[Notification Config] row (with recipients and Send Email = true) is required for an artifact to generate any email — there is no global default recipient list. On a row, Notify On Failure defaults to on, while Notify On Success and Notify On Long Running are off, so customers do not receive success or long-running noise without opting in.
Key capabilities
Flexible recipient routing
- Per-artifact overrides — route specific notifications to specific teams via the
[Cinchy].[Notification Config]table. - Group-based recipients — link Cinchy Groups as To, CC, or BCC recipients; member emails are resolved at send time, so changes to group membership take effect automatically.
- BCC Recipient Groups — a dedicated BCC group column for compliance and audit trails.
- CC and BCC support — add managers, on-call groups, or compliance mailboxes.
- Reply-To per artifact — route replies to the right team (for example,
support@for one sync,finance@for another). - From address override — use a different sender identity per config row.
- Priority levels — High, Medium, or Low per config row, mapped to email importance headers.
- Kill switch — disable all notifications globally via System Properties without redeploying.
- Deduplication across To/CC/BCC — recipients appearing in
Toare removed fromCC; recipients inToorCCare removed fromBCC. - Resolved recipients are written back — after resolution, the final To/CC/BCC lists are saved to the Outbox row for audit.
Server-side cooldown and deduplication
- A configurable Cooldown Minutes value per (artifact, trigger type) combination prevents notification storms.
- The default cooldown is 5 minutes, configurable via
DefaultCooldownMinutesinappsettings.json. - Each config row can override the default via the
Cooldown Minutescolumn. - Cooldown state is server-side (persisted in the Outbox), so it survives pod restarts.
- The cooldown key includes the trigger type, so a
LongRunningnotification does not suppress a subsequentSuccessnotification for the same artifact. Both emails fire if both checkboxes are checked.
Long-running monitors
Two parallel background services detect artifacts stuck in the Running state:
- Batch sync monitor — polls
[Execution Log]for batch syncs withState = Runningbeyond their configured threshold. Scoped to Notification Config rows whereData Sync Configis filled. - Automation monitor — polls
[Automation Execution History]for automations withStatus = Runningpast threshold. Scoped to Notification Config rows whereAutomationis filled.
Both share the same template and respect the same per-(artifact, trigger-type) cooldown. Neither applies to listener config rows — long-running is a batch concept, listeners are event-driven.
Automation monitor (Failure + Success)
A background service polls [Cinchy].[Automation Execution History] and enqueues notifications for terminal-state transitions:
- Rows transitioning to
Status = 'Failed'enqueue aFailurenotification. - Rows transitioning to
Status = 'Succeeded'enqueue aSuccessnotification.
Both queries filter by [Modified] rather than [Created], so long-running automations that transition to terminal state long after the row was inserted are still caught. Step-level execution history from [Automation Steps Execution History] is included in the email template.
Branded email templates
- Professional HTML emails rendered through Liquid (Fluid) templates.
- Cinchy brand colors built into the default template (Jetsam
#00B1FFheader, Raspberry#E7015Bfailure badges, Sunset#FFAB02warning badges, Ink#1C1A1Dbody and footer). - Default embedded Cinchy white logo in the header, overridable via the Branding table.
- Customizable company name, logo, and background color via the
[Cinchy].[Notification Branding]table. - Plain-text fallback included with all emails.
- Built-in templates include
sync-failure,sync-success,sync-partial-failure,long-running-warning,listener-down,service-down,automation-failure,automation-success, anddigest. - Subject prefix:
[Cinchy Email Notification Service].
Multiple email providers
| Provider | Provider.Type | Use case |
|---|---|---|
| SMTP | Smtp | On-prem deployments, Office 365, or any SMTP server |
| AWS SES | AwsSes | Cloud deployments on AWS (uses IAM roles, no static credentials needed) |
| Azure Communication Services | AzureCommunication | Cloud deployments on Azure (supports managed identity) |
| Microsoft Graph | MicrosoftGraph | Microsoft 365 environments where SMTP is disabled. Sends via the Graph API using a Microsoft Entra (Azure AD) app registration. |
Only one provider is active per deployment, configured via appsettings.json. Set Provider.Type to one of "Smtp", "AwsSes", "AzureCommunication", or "MicrosoftGraph" — the service fails fast at startup if Type is missing.
Reliable delivery (outbox pattern)
- Emails are persisted to a database outbox before sending.
- A background processor picks up pending emails in batches.
- Automatic retries with configurable exponential backoff (up to 5 retries by default).
- Stale claim recovery — if a pod crashes mid-send, another cycle reclaims the email.
- No email is lost once it reaches the outbox.
Safety features
- Allowed recipient domains — restrict who can receive emails in non-production environments to prevent test leaks to real customers.
- Rate limiting — client-side rate limit to stay within provider quotas (for example, AWS SES sending limits).
- Startup validation — SMTP settings are validated at startup; the service fails fast if misconfigured.
Invalid config detection
If a Notification Config row has more than one of Data Sync Config, Listener Config, or Automation filled, the service treats it as invalid and suppresses notifications with a descriptive error written to the Outbox Last Error column. Unique constraints on the table enforce one row per sync, listener, or automation.
Database tables
| Table | Purpose |
|---|---|
[Cinchy].[Notification Outbox] | Pending, sent, and failed email queue. |
[Cinchy].[Notification Config] | Per-artifact recipient routing, trigger filtering, and overrides. |
[Cinchy].[Notification Branding] | Logo (via Files table or Logo URL), background color (via System Colours), and company name. |
All tables are provisioned by the v5.20 model upgrade. New columns are auto-added on upgrade.
Notification Config columns
Show all Notification Config columns
| Column | Type | Description |
|---|---|---|
| Data Sync Config | Link → Data Sync Configurations | Batch sync to monitor. |
| Listener Config | Link → Listener Config | Event listener to monitor. |
| Automation | Link → Automations | Automation to monitor. |
| Send Email | Yes/No (default: false) | Master toggle for this config row. Must be on (and the global Notifications Enabled switch true) for the row to send. |
| Notify On Failure | Yes/No (default: true) | Send on artifact failure. |
| Notify On Success | Yes/No (default: false) | Send on artifact success. |
| Notify On Long Running | Yes/No (default: false) | Send warning if sync or automation exceeds threshold. |
| Long Running Threshold Minutes | Number | Minutes before a long-running alert triggers. |
| Recipients | Text | Semicolon-delimited email addresses. |
| CC Recipients | Text | Semicolon-delimited CC addresses. |
| BCC Recipients | Text | Semicolon-delimited BCC addresses. |
| Recipient Groups | Link → Groups (multi) | Cinchy groups resolved to member emails. |
| CC Recipient Groups | Link → Groups (multi) | Cinchy groups for CC. |
| BCC Recipient Groups | Link → Groups (multi) | Cinchy groups for BCC (compliance/audit). |
| Reply To | Text | Reply-to address override. |
| From Address Override | Text | Sender address override. |
| From Display Name Override | Text | Sender display name override. |
| Priority | Choice | High / Medium / Low. |
| Max Retries Override | Number | Override default retry count. |
| Cooldown Minutes | Number | Minimum minutes between notifications per (artifact, trigger type). |
| Max Emails Per Day | Number | Optional daily cap per artifact. |
| Subject Template | Text | Per-row subject override (Liquid). Available variables include {{sync_config_name}}, {{listener_config_name}}, {{automation_name}}, {{trigger_type}}, {{execution_id}}, {{timestamp}}, {{environment}}; LongRunning also exposes {{duration}} and {{threshold_minutes}}. Leave empty for the default subject. |
| Client Branding | Link → Notification Branding | Custom email styling. |
Exactly one of Data Sync Config, Listener Config, or Automation must be filled per row. Unique constraints on the table enforce one row per artifact.
Quick start
The simplest setup sends an email to your team whenever a specific sync fails.
Step 1. In [Cinchy].[System Properties], set the global kill switch:
| Name | Value |
|---|---|
| Notifications Enabled | true |
Step 2. In appsettings.json, configure your email provider (see Provider setup below).
Step 3. In [Cinchy].[Notification Config], add a row for the sync you want to monitor:
| Data Sync Config | Recipients | Send Email |
|---|---|---|
| Daily Import | your-team@company.com | true |
That is it. A Daily Import failure now sends an email to your-team@company.com using the default template. Add a row per artifact you want to monitor. Success and long-running notifications are suppressed by default unless you enable them on the row.
Use cases
Route different syncs to different teams
Scenario: Finance sync failures should go to the finance team, and HR sync failures should go to the HR team.
Add one [Cinchy].[Notification Config] row per sync, each with its own recipients:
| Data Sync Config | Listener Config | Recipients | Send Email |
|---|---|---|---|
| Finance Daily Import | finance-ops@company.com | true | |
| HR Payroll Sync | hr-team@company.com;hr-manager@company.com | true |
Finance Daily Import failures go to finance-ops, and HR Payroll Sync failures go to the HR team and HR manager. A sync with no Config row produces no email — add a row for each artifact you want to monitor.
Monitor event listeners
Scenario: You want to know when your real-time event listeners fail.
| Data Sync Config | Listener Config | Automation | Recipients | Send Email |
|---|---|---|---|---|
| Customer Orders Listener | integration-team@company.com | true | ||
| Inventory Updates Listener | warehouse-ops@company.com | true |
Only Notify On Failure is wired end-to-end for listener rows today. Notify On Success and Notify On Long Running may appear in the UI but will not produce emails for listener rows.
Monitor automations
Scenario: You want emails when a Cinchy automation fails, succeeds, or runs longer than expected.
| Automation | Notify On Failure | Notify On Success | Notify On Long Running | Long Running Threshold Minutes | Recipients | Send Email |
|---|---|---|---|---|---|---|
| Daily ETL | true | false | true | 30 | ops-team@company.com | true |
| Monthly Reconciliation | true | true | true | 60 | finance@company.com | true |
All three checkboxes are independent. A LongRunning notification followed by a Success notification for the same automation execution will both fire because they are separate trigger types with independent cooldowns.
Use Cinchy Groups instead of email addresses
Scenario: You do not want to maintain email addresses in the Config table; you already have Cinchy Groups with the right members.
| Data Sync Config | Recipient Groups | CC Recipient Groups | BCC Recipient Groups | Send Email |
|---|---|---|---|---|
| Finance Daily Import | Finance Ops | Finance Managers | Compliance Auditors | true |
| HR Payroll Sync | HR Team | true |
The service resolves group membership at send time via CQL link traversal (Config → Group → Users → Email Address). When someone joins or leaves the group, notifications automatically update — no Config changes needed.
You can combine Recipients (raw emails) and Recipient Groups (Cinchy groups) on the same row. Both lists are merged and deduplicated. The same applies for CC and BCC columns.
Suppress a noisy sync
Scenario: A sync is known to fail intermittently and you do not want notifications while the team investigates.
| Data Sync Config | Recipients | Send Email |
|---|---|---|
| Legacy Data Migration | false |
Failures from Legacy Data Migration are suppressed. All other syncs continue to notify normally. When the issue is resolved, flip Send Email back to true.
Trigger type filtering
Scenario: You want failure notifications for several syncs, plus success confirmations for your critical payment sync and long-running warnings for your large nightly batch. On each row, Notify On Failure defaults to on, while Notify On Success and Notify On Long Running are off until you enable them.
| Data Sync Config | Notify On Failure | Notify On Success | Notify On Long Running | Long Running Threshold Minutes | Send Email |
|---|---|---|---|---|---|
| Payment Processing Sync | true | true | false | true | |
| Nightly Warehouse Load | true | false | true | 60 | true |
| Daily Report Sync | true | false | false | true |
- A sync with no Config row → no email; a Config row with recipients and
Send Email = trueis required to send anything. - On a row where only
Notify On Failureis on, a success or long-running event produces no email.
Trigger types are independent. A LongRunning notification does not suppress a subsequent Success notification for the same artifact. The cooldown gate is keyed by (artifact, trigger type), so both emails fire if both checkboxes are checked.
Control notification frequency with cooldown
Scenario: A sync fails in a tight retry loop and you do not want 50 emails in 10 minutes.
Option A — Global default (appsettings.json):
{
"EmailNotification": {
"DefaultCooldownMinutes": 5
}
}
A five-minute cooldown applies between duplicate notifications (same artifact, same trigger type). The cooldown is server-side and persisted in the Outbox, so it survives pod restarts.
Option B — Per-sync override (Config table):
| Data Sync Config | Cooldown Minutes | Send Email |
|---|---|---|
| Flaky External API Sync | 15 | true |
| Critical Payment Sync | 1 | true |
Compliance BCC on all emails
Scenario: Your compliance team requires a copy of every notification email.
Option A — Global BCC (appsettings.json):
{
"EmailNotification": {
"DefaultBccAddress": "compliance-audit@company.com"
}
}
Every email sent by the service will BCC this address regardless of which artifact triggered it.
Option B — BCC on specific syncs (Config table):
| Data Sync Config | BCC Recipients | BCC Recipient Groups | Send Email |
|---|---|---|---|
| Financial Reporting Sync | compliance-audit@company.com | Compliance Auditors | true |
The per-row BCC and the global DefaultBccAddress are merged, not overridden. Recipients are deduplicated across To, CC, and BCC.
Different reply-to per team
Scenario: Recipients should reply to the right support inbox, not the no-reply sender.
appsettings.json:
{
"EmailNotification": {
"DefaultFromAddress": "noreply@company.com",
"DefaultReplyToAddress": "platform-support@company.com"
}
}
Per-sync override:
| Data Sync Config | Reply To | Send Email |
|---|---|---|
| Finance Daily Import | finance-support@company.com | true |
| HR Payroll Sync | hr-helpdesk@company.com | true |
Different sender identity per artifact
Scenario: Finance emails should come from finance-alerts@company.com and HR emails from hr-alerts@company.com.
| Data Sync Config | From Address Override | From Display Name Override | Send Email |
|---|---|---|---|
| Finance Daily Import | finance-alerts@company.com | Finance Platform | true |
| HR Payroll Sync | hr-alerts@company.com | HR Platform | true |
The override address must be authorized by your email provider (verified in SES, permitted by your SMTP server, and so on).
Mark critical syncs as high priority
Scenario: Some failures are urgent and should stand out in recipients' inboxes.
| Data Sync Config | Priority | Send Email |
|---|---|---|
| Payment Processing Sync | High | true |
| Daily Report Sync | Low | true |
High-priority emails carry the Importance: High header; most email clients show them with a flag or exclamation mark.
Non-production safety
Scenario: You are testing in staging and want to make sure test emails never reach real customers.
{
"EmailNotification": {
"Safety": {
"AllowedRecipientDomains": ["company.com", "testing.internal"],
"RateLimitPerMinute": 30
}
}
}
Recipients outside the allow-list are filtered out. If all recipients are filtered out, the email is marked as failed with No recipients resolved. Remove the restriction (or set the list to []) for production.
Custom email branding
Scenario: You host Cinchy for multiple clients and want their notification emails branded differently.
-
Upload logo images to the
[Cinchy].[Files]table. -
Add color entries to
[Cinchy].[System Colours](if custom colors are needed):Name Hex Value Acme Orange #FF6600 BigBank Navy #003366 -
Add rows to
[Cinchy].[Notification Branding]:Branding Name Company Name Background Color Logo Logo URL Acme Corp Acme Corporation Acme Orange acme-logo.png BigBank BigBank Financial BigBank Navy https://cdn.bigbank.example.com/logo.png Background Coloris a link to System Colours (not a plain hex value);Logois a link to the Files table (not a URL or binary upload).Logo URLis an optional public URL — when set, it takes precedence over theLogofile link. -
Link branding in
[Cinchy].[Notification Config]:Data Sync Config Client Branding Send Email Acme Daily Sync Acme Corp true BigBank Nightly Import BigBank true
To apply a global default branding, set DefaultBrandingName in appsettings.json. If left empty, the default Cinchy branding is used.
Provider setup
Only one provider is active per deployment. Pick the scenario that matches your environment below, configure only that provider's block in appsettings.json, and delete the other blocks (or leave them entirely absent). Mixing fields from multiple providers in one config file is a common source of confusion — only the active provider's block is read.
How the provider is selected
Provider.Type is required. Set it to exactly one of "Smtp", "AwsSes", "AzureCommunication", or "MicrosoftGraph". The service fails fast at startup with a clear error if it is missing or empty.
Customer deployments configure Provider.Type directly: in appsettings.json for IIS, or via the notification_provider_type field of your deployment.json (see the v5.20 Kubernetes upgrade guide) for Kubernetes.
- Do not blank out
intfields likePortwith"". The .NET configuration binder cannot convert an empty string to an integer, and the service will fail to start before logging initializes. Either delete the key entirely or set a valid number. - Leftover blocks for other providers are ignored. Only the block matching
Provider.Typeis bound and validated; others can stay in the file or be deleted — your choice. - AWS SES API vs. AWS SES SMTP relay are different code paths. Pick
"AwsSes"for the API path (uses the AWS SDK with an IAM role or access keys). Pick"Smtp"and pointSmtp.Hostatemail-smtp.<region>.amazonaws.comfor the SMTP relay — SES SMTP credentials are generated separately in the SES console and are different from IAM keys.
Each provider block also accepts its own FromAddress and FromDisplayName. When set, those values override the top-level DefaultFromAddress / DefaultFromDisplayName for that provider only — useful when, for example, AWS SES requires a verified identity that differs from the global default sender.
- SMTP
- AWS SES (IAM role)
- AWS SES (access keys)
- Azure ACS (connection string)
- Azure ACS (managed identity)
- Microsoft Graph (secret)
- Microsoft Graph (certificate)
Use for on-prem SMTP relays, Office 365 SMTP, internal mail gateways, or the AWS SES SMTP relay endpoint.
Required fields
| Field | Notes |
|---|---|
Provider.Type | Set to "Smtp" |
Smtp.Host | SMTP server hostname |
Smtp.Port | Integer (1–65535). Common: 25, 465, 587, 2525 |
Smtp.Username | Required if RequireAuth = true |
Smtp.Password | Required if RequireAuth = true |
DefaultFromAddress or Smtp.FromAddress | One must be set; the From address is required at send time |
Optional fields
| Field | Default | Notes |
|---|---|---|
Smtp.Security | StartTls | None | Auto | SslOnConnect | StartTls | StartTlsWhenAvailable |
Smtp.RequireAuth | true | If false, Username/Password are not sent |
Smtp.EhloDomain | machine hostname | Override the EHLO/HELO domain |
Smtp.ConnectionTimeoutMs | 30000 | |
Smtp.SendTimeoutMs | 30000 | |
Smtp.ValidateCertificates | true | Set false only for self-signed certs on internal relays |
Smtp.FromAddress / FromDisplayName | empty | Per-provider sender override |
Example — Office 365 SMTP
{
"EmailNotification": {
"DefaultFromAddress": "cinchy-alerts@company.com",
"DefaultFromDisplayName": "Cinchy Notifications",
"Provider": {
"Type": "Smtp",
"Smtp": {
"Host": "smtp.office365.com",
"Port": 587,
"Security": "StartTls",
"RequireAuth": true,
"Username": "cinchy-alerts@company.com",
"Password": "your-app-password"
}
}
}
}
Example — internal relay, no auth, no TLS
{
"EmailNotification": {
"DefaultFromAddress": "cinchy-alerts@company.com",
"Provider": {
"Type": "Smtp",
"Smtp": {
"Host": "mail-relay.internal",
"Port": 25,
"Security": "None",
"RequireAuth": false
}
}
}
}
Security modes (MailKit SecureSocketOptions):
| Value | Typical port | Behavior |
|---|---|---|
None | 25 | No encryption (internal relay only) |
Auto | any | MailKit auto-detects |
SslOnConnect | 465 | Implicit TLS from first byte |
StartTls | 587 | Mandatory STARTTLS upgrade |
StartTlsWhenAvailable | 587 | Opportunistic STARTTLS |
Use when running on AWS-hosted compute (EKS via IRSA, ECS task role, EC2 instance profile) with an IAM role attached to the host. The AWS SDK picks up credentials automatically from the role.
Required fields
| Field | Notes |
|---|---|
Provider.Type | Set to "AwsSes" |
AwsSes.Region | E.g. "us-east-1" |
DefaultFromAddress or AwsSes.FromAddress | Must be a verified identity in SES |
Operational prerequisites
- The IAM role on the host must have
ses:SendEmailandses:SendRawEmailpermissions (raw is used when inline images are present). - The sender identity (
DefaultFromAddressorAwsSes.FromAddress) must be a verified domain or address in SES. - If your account is still in SES sandbox mode, recipients must also be verified — see the sandbox section at the end of this tab.
Optional fields
| Field | Notes |
|---|---|
AwsSes.ConfigurationSetName | Enables SES delivery tracking (bounces, complaints, opens) |
AwsSes.FeedbackForwardingAddress | Custom Return-Path for bounce handling |
AwsSes.FromAddress / FromDisplayName | Per-provider sender override |
Do not set AwsSes.AccessKeyId or AwsSes.SecretAccessKey — leave them empty (or omit the keys). Populating them switches the provider to the explicit-credentials path.
Example
{
"EmailNotification": {
"DefaultFromAddress": "noreply@notifications.company.com",
"DefaultFromDisplayName": "Company Data Platform",
"Provider": {
"Type": "AwsSes",
"AwsSes": {
"Region": "us-east-1",
"ConfigurationSetName": "cinchy-notifications",
"FeedbackForwardingAddress": "bounce@company.com"
}
}
}
}
SES sandbox mode and production access
New AWS accounts start in SES sandbox mode. In sandbox mode, emails can only be sent to and from verified identities. Attempting to send to any unverified address fails with:
Email address is not verified. The following identities failed the check in region US-EAST-1: user@example.com
To send to any recipient, request SES production access:
- In the AWS Console, navigate to Amazon SES > Account dashboard.
- Under Sending limits, click Request production access.
- Complete the request form (transactional use case, expected daily volume, bounce-handling description).
- AWS typically reviews requests within 24 hours.
While waiting for approval, verify individual recipient addresses under Amazon SES > Configuration > Identities > Create identity > Email address.
Use when the host is not on AWS (for example, on-prem IIS or a non-AWS VM) and cannot pick up an IAM role from the environment.
Required fields
| Field | Notes |
|---|---|
Provider.Type | Set to "AwsSes" |
AwsSes.Region | E.g. "us-east-1" |
AwsSes.AccessKeyId | IAM user access key ID — must be non-empty |
AwsSes.SecretAccessKey | IAM user secret access key — must be non-empty |
DefaultFromAddress or AwsSes.FromAddress | Must be a verified identity in SES |
If either AccessKeyId or SecretAccessKey is blank, the SDK silently falls through to the IAM-role / credential-chain path — so on a non-AWS host, both keys must be present.
Operational prerequisites
- The IAM user owning the keys must have
ses:SendEmailandses:SendRawEmailpermissions. - The sender identity must be verified in SES (same rules as the IAM-role scenario above).
Optional fields are the same as the IAM-role scenario: ConfigurationSetName, FeedbackForwardingAddress, FromAddress, FromDisplayName.
Example
{
"EmailNotification": {
"DefaultFromAddress": "noreply@notifications.company.com",
"Provider": {
"Type": "AwsSes",
"AwsSes": {
"Region": "us-east-1",
"AccessKeyId": "<aws-access-key-id>",
"SecretAccessKey": "<aws-secret-access-key>"
}
}
}
}
Use when the host cannot use Azure managed identity (for example, deployments outside Azure).
Required fields
| Field | Notes |
|---|---|
Provider.Type | Set to "AzureCommunication" |
AzureCommunication.ConnectionString | From the ACS resource's Keys blade |
AzureCommunication.UseManagedIdentity | false (default) |
DefaultFromAddress or AzureCommunication.FromAddress | Must use a verified MailFrom domain on the ACS Email Communication Service |
Optional fields
| Field | Notes |
|---|---|
AzureCommunication.FromAddress / FromDisplayName | Per-provider sender override |
Do not set AzureCommunication.Endpoint — it is ignored when UseManagedIdentity = false.
Example
{
"EmailNotification": {
"DefaultFromAddress": "DoNotReply@abc123.azurecomm.net",
"DefaultFromDisplayName": "Company Data Platform",
"Provider": {
"Type": "AzureCommunication",
"AzureCommunication": {
"ConnectionString": "endpoint=https://my-acs.unitedstates.communication.azure.com/;accesskey=...",
"UseManagedIdentity": false
}
}
}
}
Use when running on Azure (Azure VM, App Service, AKS) with a managed identity granted a sender role on the ACS resource. This is the recommended production setup on Azure — no secrets in config.
Required fields
| Field | Notes |
|---|---|
Provider.Type | Set to "AzureCommunication" |
AzureCommunication.Endpoint | E.g. https://my-acs.unitedstates.communication.azure.com |
AzureCommunication.UseManagedIdentity | true |
DefaultFromAddress or AzureCommunication.FromAddress | Must use a verified MailFrom domain on the ACS resource |
Operational prerequisites
- Enable a system-assigned or user-assigned managed identity on the host.
- Grant the identity an appropriate sender role on the ACS Email Communication Service (for example,
Contributoror a custom role with the requiredMicrosoft.Communication/CommunicationServices/Email/Send/action).
Do not set AzureCommunication.ConnectionString — it is ignored on this path.
Example
{
"EmailNotification": {
"DefaultFromAddress": "DoNotReply@abc123.azurecomm.net",
"Provider": {
"Type": "AzureCommunication",
"AzureCommunication": {
"Endpoint": "https://my-acs.unitedstates.communication.azure.com",
"UseManagedIdentity": true
}
}
}
}
For Microsoft 365 environments where SMTP submission is disabled. The service sends through the Microsoft Graph API using a Microsoft Entra (Azure AD) app registration.
Required fields
| Field | Notes |
|---|---|
Provider.Type | Set to "MicrosoftGraph" |
MicrosoftGraph.TenantId | Microsoft Entra tenant ID |
MicrosoftGraph.ClientId | App registration (client) ID |
MicrosoftGraph.ClientSecret | Client secret for the app registration |
MicrosoftGraph.SenderUserPrincipalName | UPN of the mailbox to send AS (shared mailbox works — no license required) |
Operational prerequisites
- Create a Microsoft Entra app registration.
- Grant it
Mail.Sendas an application permission (not delegated). - Have a tenant admin grant admin consent for that permission.
- Generate a client secret on the app registration and copy the secret value (not the secret ID) into
ClientSecret.
Optional fields
| Field | Notes |
|---|---|
MicrosoftGraph.FromAddress | Defaults to SenderUserPrincipalName if empty |
MicrosoftGraph.FromDisplayName | Per-provider display-name override |
Leave MicrosoftGraph.CertificateThumbprint empty — populating it switches to the certificate path.
Example
{
"EmailNotification": {
"DefaultFromDisplayName": "Cinchy Notifications",
"Provider": {
"Type": "MicrosoftGraph",
"MicrosoftGraph": {
"TenantId": "<entra-tenant-id>",
"ClientId": "<app-registration-client-id>",
"ClientSecret": "<client-secret-value>",
"SenderUserPrincipalName": "noreply@company.com"
}
}
}
}
Same scenario as Microsoft Graph with a client secret, but uses a certificate for app authentication. Recommended over client secrets for production.
Required fields
| Field | Notes |
|---|---|
Provider.Type | Set to "MicrosoftGraph" |
MicrosoftGraph.TenantId | Microsoft Entra tenant ID |
MicrosoftGraph.ClientId | App registration (client) ID |
MicrosoftGraph.CertificateThumbprint | SHA-1 thumbprint of a certificate installed in the host's CurrentUser store |
MicrosoftGraph.SenderUserPrincipalName | UPN of the mailbox to send AS |
Operational prerequisites
- All app registration / consent steps from the client-secret scenario.
- Generate a certificate (self-signed is acceptable), install the certificate (private key included) in the CurrentUser store on the host running the Notification Service.
- Upload the public-key (
.cer) of the certificate to the app registration's Certificates & secrets blade.
Leave MicrosoftGraph.ClientSecret empty.
Example
{
"EmailNotification": {
"DefaultFromDisplayName": "Cinchy Notifications",
"Provider": {
"Type": "MicrosoftGraph",
"MicrosoftGraph": {
"TenantId": "<entra-tenant-id>",
"ClientId": "<app-registration-client-id>",
"CertificateThumbprint": "<installed-cert-sha1-thumbprint>",
"SenderUserPrincipalName": "noreply@company.com"
}
}
}
}
Resilience configuration
Per-send resilience
{
"EmailNotification": {
"Resilience": {
"MaxRetryAttempts": 3,
"TimeoutSeconds": 30,
"RetryBaseDelaySeconds": 2,
"UseJitter": true
}
}
}
This controls retries within a single send attempt (for example, transient SMTP connection failures). These are separate from the outbox-level retries below.
Outbox-level retries
{
"EmailNotification": {
"Outbox": {
"PollingIntervalSeconds": 10,
"BatchSize": 10,
"MaxRetries": 5,
"RetryBaseDelaySeconds": 30,
"RetryBackoffMultiplier": 4.0,
"ClaimTimeoutMinutes": 5,
"StartupDelaySeconds": 5,
"QueryTimeoutSeconds": 30
}
}
}
| Setting | Default | Description |
|---|---|---|
PollingIntervalSeconds | 10 | How often the processor and monitors check for pending work. |
BatchSize | 10 | Max emails claimed per polling cycle. |
MaxRetries | 5 | Total send attempts before permanent failure. |
RetryBaseDelaySeconds | 30 | Base delay for exponential backoff. |
RetryBackoffMultiplier | 4.0 | Multiplier for exponential backoff. |
ClaimTimeoutMinutes | 5 | Reclaim entries stuck in Sending state. |
StartupDelaySeconds | 5 | Delay before the processor starts after service boot. |
QueryTimeoutSeconds | 30 | Per-query timeout for outbox reads/writes and cooldown checks, so a slow or locked database cannot stall the polling loop. |
Configuration hierarchy
Settings are resolved in this order — the first non-empty value wins:
| Setting | First (highest priority) | Second | Third (fallback) |
|---|---|---|---|
| Recipients | Config table row (text + groups merged) | — | (none — must be set on the Config row) |
| CC | Config table row (text + groups merged) | — | (none) |
| BCC | Config table row (text + groups merged) | merged with | appsettings.DefaultBccAddress |
| Reply-To | Config table row | — | appsettings.DefaultReplyToAddress |
| From Address | Config table row | — | appsettings.DefaultFromAddress |
| From Display Name | Config table row | — | appsettings.DefaultFromDisplayName |
| Priority | Config table row | — | (none — normal) |
| Max Retries | Config table row | — | appsettings.Outbox.MaxRetries |
| Cooldown Minutes | Config table row | — | appsettings.DefaultCooldownMinutes (default: 5) |
| Branding | Config table Client Branding link | — | appsettings.DefaultBrandingName → hardcoded defaults |
| Send Email | Config table row | — | Blocked when global Notifications Enabled = 0 (additional gate, not a fallback source) |
| Trigger filtering | Config table row (if exists) | — | Default: Failure-only |
BCC is special: the per-row BCC and the global DefaultBccAddress are merged, not overridden. This ensures compliance BCC is always applied even when an artifact has its own BCC.
There is no "catch-all" row and no global default recipient list. Recipients must be set per artifact on the Config row (via Recipients text or Recipient Groups). Global defaults for cooldown and branding are handled through appsettings.json.
Infrastructure settings reference
Show all infrastructure (appsettings.json) settings
| Setting | Purpose |
|---|---|
DefaultFromAddress | Default sender email for all emails. |
DefaultFromDisplayName | Default sender display name. |
DefaultReplyToAddress | Default reply-to address. |
DefaultBccAddress | Default BCC for compliance/audit. |
DefaultBrandingName | Default branding config name (from Branding table). |
DefaultCooldownMinutes | Default cooldown between duplicate notifications (default: 5). |
Provider.Type | Smtp, AwsSes, AzureCommunication, or MicrosoftGraph. |
Provider.Smtp.Security | TLS mode (see Provider setup). |
Provider.Smtp.ConnectionTimeoutMs | SMTP connection timeout (default: 30000). |
Provider.Smtp.SendTimeoutMs | SMTP send timeout (default: 30000). |
Provider.Smtp.EhloDomain | Custom EHLO domain for SMTP. |
Provider.Smtp.ValidateCertificates | TLS certificate validation (default: true). |
Provider.Smtp.RequireAuth | Require SMTP authentication (default: true). |
Provider.AwsSes.ConfigurationSetName | SES configuration set for delivery tracking. |
Provider.AwsSes.FeedbackForwardingAddress | SES bounce/complaint address. |
Provider.AzureCommunication.UseManagedIdentity | Use managed identity auth. |
Provider.AzureCommunication.Endpoint | ACS endpoint for managed identity. |
Resilience.MaxRetryAttempts | Per-send retry attempts (default: 3). |
Resilience.TimeoutSeconds | Per-send timeout (default: 30). |
Resilience.RetryBaseDelaySeconds | Per-send retry base delay (default: 2). |
Resilience.UseJitter | Add jitter to retry delays (default: true). |
Outbox.RetryBaseDelaySeconds | Outbox retry base delay (default: 30). |
Outbox.RetryBackoffMultiplier | Outbox retry backoff multiplier (default: 4.0). |
Safety.AllowedRecipientDomains | Domain allow-list for non-production. |
Safety.RateLimitPerMinute | Client-side rate limit. |
Health endpoints
The service exposes three HTTP endpoints for monitoring and verification. On Kubernetes they back the liveness and readiness probes; on IIS they are reachable under the /notification sub-application path.
| Endpoint | Purpose |
|---|---|
GET /healthcheck | Detailed status — returns the component name, build identifier, and individual checks for Cinchy connectivity, outbox table access, and the email provider. |
GET /health | Liveness — returns { "status": "Green" } when the service is running. |
GET /health/ready | Readiness — confirms the service can read the outbox table. |
Known limitations
- Crash before enqueue. If a sync pod is OOM-killed mid-execution, the webhook code never runs. The heartbeat monitor partially mitigates this by detecting stale services. Automation Failure, Success, and LongRunning notifications are monitor-driven and unaffected.
- Single notification service pod. The service runs as one replica. If it is down, outbox entries queue up and are processed when it recovers.
- Listener Success and Listener LongRunning are not wired. The Notification Config UI exposes both checkboxes for listener rows, but
Notify On Successfor a listener has no upstream emitter today, andNotify On Long Runningis intentionally out of scope for listeners. - No step-level automation notifications. Only automation-level rollups are produced. Step status is included inside the automation emails as a step breakdown table; no separate per-step email is produced.
- Cooldown is window-based, not per-execution. A second failure of the same automation within the cooldown window collapses to one email. This is usually desirable for anti-spam.