Welcome to ASAB Iris, your go-to multifunctional messenger microservice, designed for automated document rendering and message dispatching across various communication channels!
Overview
- Craft emails with Jinja, Markdown or HTML templates.
- Personalize recipients, subject, and body via template parameters.
- Trigger through the single
/send_emailendpoint (HTTP or Kafka).
ASAB Iris supports sending email using Microsoft 365 Graph API in two modes:
mode = appβ Application Permissions (default)mode = delegatedβ User-delegated OAuth flow (requires one-time browser login)
Provider selection for /send_email currently works like this:
- If
[smtp]is configured, Iris uses SMTP. - If SMTP is not configured and
[m365_email]is complete, Iris uses MS365. - If neither provider is configured,
/send_emailis unavailable.
This means delegated MS365 matters only when SMTP is not configured.
Use this mode when Iris sends email as the application, with no user interaction.
[m365_email]
mode = app ; optional, "app" is default
tenant_id = YOUR_AZURE_TENANT_ID
client_id = YOUR_APP_CLIENT_ID
client_secret = YOUR_APP_CLIENT_SECRET
user_email = sender@yourdomain.com
subject = Default MS365 Subject
api_url = https://graph.microsoft.com/v1.0/users/{}/sendMailCharacteristics
- No
/authorize_ms365required - Suitable for backend automation
- Requires Application permission
Mail.Send - Runs fully headless
Use this mode when Iris must send email on behalf of a signed-in Microsoft 365 user.
[m365_email]
mode = delegated
tenant_id = YOUR_AZURE_TENANT_ID
client_id = YOUR_APP_CLIENT_ID
client_secret = YOUR_APP_CLIENT_SECRET
user_email = sender@yourdomain.com
redirect_uri = https://iris.example.com/api/asab-iris/authorize_ms365
subject = Default MS365 Subject
api_url = https://graph.microsoft.com/v1.0/users/{}/sendMail-
Open this in a browser:
https://iris.example.com/api/asab-iris/authorize_ms365 -
Iris redirects to Microsoft Login
-
User signs in
-
Microsoft calls back with
?code=... -
Iris exchanges the code β saves:
- access_token
- refresh_token
-
MS365 email sending now works silently until the delegated token must be refreshed or re-authorized
In deployments with ZooKeeper configured, delegated tokens are persisted and reused after restart.
This applies when MS365 is the active email provider for /send_email (that is, SMTP is not configured).
If tokens are missing or expired, /send_email returns:
{
"result": "ERROR",
"error": "IrisError|ms365_delegated_auth_required",
"error_dict": {
"authorize_url": "/authorize_ms365",
"reason": "Iris is configured for delegated MS365 email, but there is no valid delegated token.",
"what_to_do": "Open '/authorize_ms365' in a browser and sign in with the Microsoft 365 account that should send emails."
}
}redirect_uri is environment-dependent. Build it from the public browser-reachable URL of Iris, not from an internal container/service address.
Rule:
redirect_uri = <public base URL of Iris> + /authorize_ms365
If Iris is published behind a reverse proxy or ingress under a path prefix, that prefix must be included in the public base URL.
Examples:
Without path prefix:
https://iris.example.com/authorize_ms365
Behind reverse proxy or ingress with path prefix:
https://iris.example.com/api/asab-iris/authorize_ms365
For delegated MS365 to work, the value must be identical in both places:
- Azure App Registration Add the exact callback URL as a Redirect URI.
- Iris configuration
Set the same exact value in
[m365_email] redirect_uri.
Example:
[m365_email]
mode=delegated
redirect_uri=https://iris.example.com/api/asab-iris/authorize_ms365If these values differ even slightly (scheme, host, path, or proxy prefix), Microsoft login will redirect back incorrectly or token exchange will fail.
- The App Registration must have Delegated permission:
Mail.Send - Redirect URI must exactly match your
redirect_uri redirect_urimust be the public browser-reachable callback URL for that environment.- The signed-in user must be allowed to send mail from
user_email
Note
- SMTP supports attachments and will include them in the outbound message.
- MS365 also supports attachments and sends them as Graph API file attachments.
- All email templates must live under
/Templates/Email/.
Example Web Request
PUT /send_email
Content-Type: application/json
{
"to": ["alice@example.com", "bob@example.com"],
"from": "noreply@yourdomain.com",
"subject": "Monthly Report",
"body": {
"template": "/Templates/Email/report.md",
"params": {
"name": "Alice",
"month": "June"
}
},
"attachments": [
{
"template": "/Templates/Email/summary.html",
"params": {},
"format": "pdf",
"filename": "summary.pdf"
}
]
}If
[smtp]is configured, this request will be sent via SMTP. Otherwise, if[m365_email]is configured, it will be sent via MS365.
Configuration
[email]
markdown_wrapper=/Templates/Email/body_wrapper.htmlmarkdown_wrapper: Specifies the path to the HTML template for wrapping email content. If this configuration is not provided, or if the value is left empty, the markdown_wrapper will default to None. In such cases, Markdown-formatted emails will be sent without any additional HTML wrapping. This means the emails will consist solely of the content converted from Markdown to HTML, without any extra styling or structure provided by a wrapper template.
Keys (under [smtp])
# Transport
host = smtp.example.com
port = 465 ; 465 = implicit TLS (SMTPS), 587 = STARTTLS
user = admin
password = secret
from = info@example.com
# TLS mode (choose ONE)
ssl = yes ; yes => implicit TLS (465)
starttls = no ; yes => STARTTLS (587). Do NOT enable both.
# Verification
validate_certs = yes ; Verify server certificate (recommended in prod)
cert_bundle = ; Path to CA/chain PEM when validate_certs=yes.
; Needed for self-signed/internal CAs.
; Leave empty to use system trust store.
; When validate_certs=no, both chain and hostname checks are disabled and
; cert_bundle is ignored.
Important
- Use real booleans:
yes|no(not quoted strings). cert_bundlemust be a CA/chain PEM, not the server cert.- If you supply a custom TLS context in code, some clients ignore
validate_certs/cert_bundle.
Common Setups
A) Production β SMTPS (465) with internal CA
[smtp]
host = mail.internal.lan
user = admin
port = 465
ssl = yes
starttls = no
password = ****
from = noreply@internal.lan
validate_certs = yes
cert_bundle = /etc/ssl/internal_ca.pemB) Production β STARTTLS (587) with public CA
[smtp]
host = smtp.example.com
user = admin
port = 587
ssl = no
starttls = yes
password = ****
from = noreply@example.com
validate_certs = yes
cert_bundle = ; empty = use system trust storeC) Local Dev β no TLS (not for prod)
[smtp]
host = 127.0.0.1
user = admin
port = 2525
ssl = no
starttls = no
password =
from = dev@localhost
validate_certs = no
cert_bundle =Overview
- Send messages to a Slack via HTTP REST API or through Kafka Topic
- Apply Jinja2 templates.
- Trigger them through a web handler or an Apache Kafka message - flexibility is key!
Configuration
[slack]
token=xoxb-111111111111-2222222222222-3333333333333voe
channel=generalExplanation
token: Your Slack OAuth access token.channel: The Slack channel you want to send messages to.
- A Slack workspace where you're the admin of app creation.
- Visit the Slack API website.
- Select "Create New App".
- Name your app and choose its home (workspace).
- Select "Create App"
- Configure "OAuth & Permissions".
- Add the necessary scopes:
chat:write,files:write,files:readandchannels:read. - Select "Install to Workspace".
- "Allow"
- Copy the OAuth access token.
Add following scopes for:
groups:read: for posting to private channelsim:read: for posting to direct messsagesmpim:read: for posting to group direct messages
- Choose a channel in your Slack workspace.
- Invite the app.
- Search for your app and select it.
- Confirm the addition.
- Verify the app's presence.
Overview
- Send messages to a Microsoft Teams via HTTP REST API or thru Kafka Topic
- Apply Jinja2 templates.
- Trigger them through a web handler or an Apache Kafka message - flexibility is key!
Configuration
[msteams]
webhook_url=https://outlook.office.com/webhook/...Explanation
webhook_url: Your webhook URL.
asab-iris implements specific error handling strategies for the Kafka and Web handlers to ensure robustness and reliability.
- Fallback Mechanism: In case of errors during message processing, a fallback mechanism is triggered. This ensures that the system can still operate or recover gracefully when encountering issues.
- General Error Logging: Any errors during message dispatching are logged as exceptions.
- Error Responses: Internal error codes are mapped to HTTP status codes, providing meaningful responses to clients.
- Exception Handling: General exceptions are logged, and standardized error responses are sent to clients.
Overview
- Send SMS messages using the SMSBrana.cz API.
- Supports automatic segmentation of long messages.
- Apply Jinja2 templates.
- Trigger them through a web handler or an Apache Kafka message - flexibility is key!
Configuration
[sms]
login=your_smsbrana_login
password=your_smsbrana_password
timestamp_format=%Y%m%dT%H%M%S
api_url=https://api.smsbrana.cz/smsconnect/http.phpExplanation
login: Your login for the SMSBrana.cz service.password: Your password for the SMSBrana.cz service.timestamp_format: The format used for timestamps, typically"%Y%m%dT%H%M%S".api_url: The API URL for SMSBrana.cz, typicallyhttps://api.smsbrana.cz/smsconnect/http.php.
Handling Long Messages
- SMS messages are automatically split into segments if they exceed the length limits:
- 1 SMS: up to 160 characters.
- 2 SMS: up to 306 characters (153 characters per segment).
- 3 SMS: up to 459 characters (153 characters per segment).
- Each segment is sent separately, ensuring that messages are not truncated.
Usage Example
To send an SMS, you can configure the SMS service in your application and trigger it via a web handler or Kafka message. The service will handle splitting long messages into segments and sending them sequentially.
Error Handling
- Invalid Phone Number: If the phone number is missing or invalid, an
ASABIrisErroris raised. - Non-ASCII Characters: If the message contains non-ASCII characters, an
ASABIrisErroris raised. - Service Errors: Errors returned by the SMSBrana.cz API are mapped to custom error messages and logged for troubleshooting.
Overview
ASAB Iris supports real-time push notifications using the ntfy.sh protocol (or a self-hosted ntfy server). This lets you send instant alerts to mobile devices, desktops, or browsers β perfect for ticketing, monitoring, or system events.
- Trigger via HTTP REST API or Kafka.
- Uses Jinja2 templates just like email/Slack/Teams/SMS.
- Allows a fallback/default topic.
- Supports configurable request timeout.
[push]
url = https://ntfy.sh ; Base URL of the ntfy server
default_topic = send_ph ; Default topic when request doesn't specify one
timeout = 10 ; HTTP request timeout (seconds)Explanation
url: Base ntfy endpoint (cloud or self-hosted).default_topic: IRIS uses this topic if the message doesnβt specify one.timeout: Max seconds to wait for ntfy response before aborting the request.
-
A topic (e.g.,
alerts,tickets,errors) is created simply by naming it β ntfy topics are auto-created. -
Users subscribe through:
- ntfy mobile app (Android/iOS)
- ntfy desktop app
- Browser subscription
- CLI
-
IRIS posts the rendered message to that topic.
-
All subscribers receive the notification instantly.
PUT /send_push
Content-Type: application/json
{
"topic": "alerts",
"body": {
"template": "/Templates/Push/alert.md",
"params": {
"severity": "High",
"service": "Billing",
"message": "Payment processor timeout"
}
}
}- If
topicis missing, IRIS falls back to[push] default_topic.
{
"type": "push",
"topic": "tickets",
"body": {
"template": "/Templates/Push/ticket_update.md",
"params": {
"ticket_id": "INC-3021",
"status": "Resolved"
}
}
}Explanation
type: Must be"push".topic: Optional; overrides[push] default_topic.body.template: Path to your template.params: Values passed into the template.
Place all templates under:
/Templates/Push/
Example template (alert.md):
π¨ *{{ severity }} Alert*
Service: **{{ service }}**
Message: {{ message }}
{{ now()|datetimeformat("%Y-%m-%d %H:%M:%S") }}Push notifications may include optional metadata under body.params.
These parameters affect how the notification is displayed, not the message content.
Supported parameters (backend-specific):
titleβ notification titlepriorityβ urgency leveltagsβ labels or icons shown by the clientclickβ URL opened when the notification is clicked
Example:
{
"topic": "tenantA-alerts",
"rendered_message": "CPU usage above 90% on node ant-3",
"body": {
"params": {
"title": "High CPU",
"priority": "high",
"tags": "prod,alerts",
"click": "https://grafana.example.com/d/abc123"
}
}
}Notes:
- Metadata is optional.
- Metadata affects presentation only; message content remains unchanged.
- Visual appearance may differ between tenants, requests, and clients.
- Metadata support is push-backend specific (currently
ntfy). - Identical visual rendering is not guaranteed.
- Inbound: HTTP REST API
- Inbound: Apache Kafka
- Jinja2 for template specifications
- Markdown for templates
- HTML for templates and the output
- PDF for the output
- Output: Email SMTP
- Output: Slack
- Output: Microsoft Teams
- Powered by ASAB (because weβre standing on the shoulders of giants)
Diagram: Architecture
The now() function returns the current UTC datetime. You can use this function in your Jinja2 templates to insert the current date and time.
Usage Example:
{{ now() }}This will output the current date and time in the default format.
The datetimeformat filter allows you to format datetime objects in your Jinja2 templates.
Usage Example:
To format the current datetime returned by now():
{{ now()|datetimeformat('%Y-%m-%d %H:%M:%S') }}Possible Formats:
Here are some common format strings you can use with the datetimeformat filter:
%Y-%m-%d %H:%M:%S- Outputs as2024-06-17 14:45:30%d-%m-%Y- Outputs as17-06-2024%A, %d %B %Y- Outputs asMonday, 17 June 2024%I:%M %p- Outputs as02:45 PM%B %d, %Y- Outputs asJune 17, 2024%Y-%m-%d- Outputs as2024-06-17
Overview
ASAB Iris supports sending notifications to various communication channels such as email, Slack, and Microsoft Teams via Kafka. Each notification is structured using templates and parameters to ensure customizable content.
- Notifications are sent to Kafka in JSON format.
- Each notification contains a
typefield specifying the channel (e.g.,email,slack,msteams), and abodythat includes the template and its parameters.
Example Kafka Message:
{
"type": "email",
"to": ["Shivashankar <mithunshivashankar@gmail.com>"],
"from": "info@teskalabs.com",
"body": {
"template": "/Templates/Email/message.md",
"params": {
"name": "I am testing a template",
"error": "None"
}
}
}Explanation:
type: Defines the notification type asemail.to: List of recipients.from: The sender's email address.template: Path to the template used for the email content.params: Parameters for populating the email template.
Example Kafka Message:
{
"type": "slack",
"body": {
"template": "/Templates/Slack/Slack example.md",
"params": {
"name": "I am testing a template",
"error": "None"
}
}
}Explanation:
type: Defines the notification type asslack.body.template: Path to the Slack message template.params: Parameters for populating the Slack template.
Example Kafka Message:
{
"type": "msteams",
"body": {
"template": "/Templates/MSTeams/Teams example.md",
"params": {
"name": "I am testing a template",
"error": "None"
}
}
}Explanation:
type: Defines the notification type asmsteams.body.template: Path to the Microsoft Teams message template.params: Parameters for populating the Teams template.