The alert
service sends reports via email based on conditions.
First, set up an email service. Here is a sample:
email:
gramex-guide-gmail:
type: gmail # Type of email used is GMail
email: gramex.guide@gmail.com # Generic email ID used to test e-mails
password: tlpmupxnhucitpte # App-specific password created for Gramex guide
Email scheduling uses the same keys as scheduler: minutes
,
hours
, dates
, weekdays
, months
and years
.
alert:
alert-schedule:
days: "*" # Send email every day
hours: "6, 12" # at 6am and 12noon
minutes: 0 # at the 0th minute, i.e. 6:00am and 12:00pm
utc: true # GMT (or UTC). Set false for local server time
to: admin@example.org
subject: Scheduled alert
body: This email will be scheduled and sent as long as Gramex is running.
To send an email on startup, use startup:
instead of days:
, hours:
, etc.
This sends an email every time Gramex starts.
You can use startup:
and the schedule (days:
, hours:
, etc) together.
This will send an email on startup and at the specified schedule.
(Before Gramex 1.31, emails without a schedule were sent out once automatically.
This led to work-in-progress messages being emailed. From Gramex 1.31, all mails
require a startup:
or a schedule.)
condition: once(..)
ensures that an alert campaign is sent out only once.
For example:
alert:
alert-condition-once:
condition: once(r'$YAMLPATH', 'unique-key')
...
… will send out the email only once, unless the 'unique-key'
is changed.
from:
lets you choose a different user to send as. Note: this won’t work
on GMail unless you enable
Send emails from a different address or alias
alert:
alert-as-user:
to: "User name <admin@example.org>"
from: "Sender name <sender@example.org>"
subject: Alert from Gramex
body: This email is sent as if from sender@example.org
reply_to:
picks a different user to send a reply to. When the user gets the
email, it is from the from:
ID or the email service account. But when the
user replies, the reply is sent to the reply_to:
address. For example:
alert:
alert-as-user:
to: "User name <admin@example.org>"
reply_to: "Reply Person <reply@example.org>"
subject: Alert from Gramex
body: Replies to this email will be addressed to reply@example.org
You may also use the on_behalf_of:
header to indicate that you’re sending the
alert on behalf of a different user.
The to:
, cc:
and bcc:
fields accept a list or comma-separated email IDs.
alert:
alert-email:
service: email-service
to:
- Admin <admin@example.org>
- "Admin 2 <admin2@example.org>"
cc: cc@example.org, cc2@example.org
bcc: cc@example.org, cc2@example.org
subject: Gramex started
body: |
This email is sent to multiple people. The to:, cc:, bcc: fields
accept a list or a comma-separated string of email IDs.
Preview alert-email
in Admin > Alerts.
html:
specifies the HTML content to be sent. body:
can be used along with
HTML. Email clients choose which content to render based on their capability.
alert:
alert-html:
to: admin@example.org
subject: HTML email
body: This content will only be displayed on devices that cannot render HTML email. That's rare.
html: <p>This content will be shown in <em>HTML</em> on <strong>supported devices</strong>.
Preview alert-html
in Admin > Alerts.
markdown:
can be used to specify the HTML content as Markdown instead of
html
(and overrides it).
alert:
alert-markdown:
to: admin@example.org
subject: Markdown email
body: This content will only be displayed on devices that cannot render HTML email. That's rare.
markdown: |
This is Markdown content.
Markup like *emphasis* and **strong** are supported.
Preview alert-markdown
in Admin > Alerts.
bodyfile:
, htmlfile:
and markdownfile:
load content from files instead of
directly typing into the body
, html
or markdown:
keys.
alert:
alert-content-file:
to: admin@example.org
subject: HTML email from file
bodyfile: $YAMLPATH/email.txt # Use email.txt in current directory
htmlfile: $YAMLPATH/email.html # Use email.html in current directory
markdownfile: $YAMLPATH/email.md # Use email.md in current directory
Preview alert-content-file
in Admin > Alerts..
images:
specifies one or more <key>: <url>
entries. Each <key>
can be
embedded into the email as an image using <img src="cid:<key>">
. The <url>
can be a file path or a URL.
alert:
alert-images:
to: admin@example.org
subject: Inline images
markdown: |
<p>This email has 2 inline images.</p>
<p><img src="cid:img1"></p>
<p><img src="cid:img2"></p>
images:
img1: $YAMLPATH/../uicomponents/bg-small.png
img2: https://en.wikipedia.org/static/images/wikimedia-button.png
Preview alert-images
in Admin > Alerts.
attachments:
specifies one or more <key>: <url>
entries. Each entry is added
to the email as an attachment. The <url>
can be a file path or a URL.
alert:
alert-attachments:
to: admin@example.org
subject: Email with attachments
html: This email contains attachments.
attachments:
- $YAMLPATH/doc1.docx
- https://example.org/sample.pptx
Preview alert-attachments
in Admin > Alerts.
The to
, cc
, bcc
, from
, subject
, body
, html
, bodyfile
, htmlfile
fields can all use Tornado templates to dynamically generate values.
alert:
alert-templates:
to: '{{ "admin@example.org" }}'
subject: Template email
html: |
{% import sys %}
<p>This email was sent from {{ sys.platform }}.</p>
<p><img src="cid:img"></p>
<p>{% raw open(r'$YAMLPATH/email.html').read() %}</p>
images:
img: '{% import os %}{{ os.path.join(r"$YAMLPATH", "../uicomponents/bg-small.png") }}'
attachments:
- '{% import os %}{{ os.path.join(r"$YAMLPATH", "doc1.docx") }}'
Tornado templates escape all HTML content. To pass the HTML content raw,
use {% raw expression %}
instead of {{ expression }}
.
The templates can use these variables:
config
: the alert configuration as an AttrDict. For example,
config.subject
is the subject configurationdata
holds the dataset{<key>: ...}
,<key>
holds the respective dataseteach:
is used, then the following variables are also available:row
: the data for each entry that each:
loops throughindex
: the key or index number of each entryargs
: If you run alert.run(args=...)
via alert API
it holds whatever value was passed. It defaults to an empty dict {}
.To send a dashboard as an inline-image or an attachment, set up a CaptureHandler, then use its URL as the image and/or attachment.
alert:
alert-capture:
to: admin@example.org
subject: Dashboard attachment
html: <h1>Sample dashboard</p>
<p><img src="cid:img"></p>
images:
img: http://server/capturehandler/?url=http://server/dashboard&ext=png
attachments:
- http://server/capturehandler/?url=http://server/dashboard&ext=pdf
# Optional: to capture the dashboard as a specific user, add this user section
# user:
# id: user@example.org
# email: {{ config['to'] }} # User object values can be templates
# role: manager
Note: If the server that sends the alert is the same as the the server that
runs CaptureHandler, you must specify thread: true
.
The user:
section sends an X-Gramex-User header to
take a screenshot of a dashboard as the user would have seen it. Specify the
entire user
object here. Values in user:
can be templates.
data:
specifies one or more datasets. You can use these in templates to create
dynamic content based on data. The data:
can be:
data: file.xlsx
loads data from a CSV or Excel file as a DataFramedata: {url: ..., table: ...}
loads data from a SQLAlchemy URL and tabledata: [... list of objects ...]
defines data as a list of objects in the YAML fileThe data is available to templates in a variable called data
.
alert:
alert-templates:
data:
- { month: Jan, sales: 100 }
- { month: Feb, sales: 110 }
- { month: Mar, sales: 90 }
to: admin@example.org
subject: "Email generated from data"
html: 'Total sales was {{ sum(row["sales"] for row in data) }}'
Multiple datasets can be defined as a dict containing different keys. Here is an example that covers all variations:
alert:
data:
plainlist:
- { month: Jan, sales: 100 }
- { month: Feb, sales: 110 }
- { month: Mar, sales: 90 }
sales: $YAMLPATH/sales.xlsx
employees:
url: mysql://root@localhost/hr
table: employee
This data can be used directly in templates as variables named plainlist
,
sales
and employee
.
each:
specifies a dataset to iterate over. Each row becomes a separate email.
condition:
is a Python expression or pipeline that filters the data and returns the final
rows to send as email. If the result is False
, []
, {}
or any false-y
value, the alert will exit.
Here is an example of a birthday alert email sent every day. (This assumes that
birthday.csv
has a column called birthday
which has a format like
10-Aug-2018
.)
alert:
alert-birthday:
data: $YAMLPATH/birthday.csv
condition: data[data['birthday'] == datetime.datetime.today().strftime('%d-%b-%Y')]
each: data # Loop through each row, i.e. person whose birthday is today
to: '{{ row['email'] }}' # Use the "email" column for the person's email ID
subject: Happy birthday {{ row['name'] }} # Use the "name" column for the person's name
days: '*' # Schedule birthday mail every day
hours: 6 # at 6:00am local time
minutes: 0
Send an alert to each sales person, but only if they did not meet the target.
alert:
alert-sales-target:
data:
sales:
url: mysql+pymysql://user:password@server/database
query: 'SELECT * FROM sales'
condition: sales[sales['target'] < sales['value']]
each: sales
to: '{{ row['email'] }}'
cc: salesmanager@example.org
subject: Sales target deficit of {{ row['target'] - row['value'] }}
To send a notification alert after your alert(s) are done, use notify:
.
notify:
is a list of alerts to trigger after this alert runs.
The notification alerts get an args
data variable. This holds the history of
notification successes and failures. For example:
alert:
alert-mail:
to: "user@example.org"
data: ...
each: ...
subject: ...
notify: [alert-notify, alert-success, alert-failure]
alert-notify: # Send notification after alert
to: "admin@example.org"
subject: 'Sent {{ len(args["done"]) }} alerts, failed {{ len(args["fail"]) }} alerts'
alert-failure:
condition: args["fail"] # Send notification only if there's a failure
to: "admin@example.org"
subject: 'Failed on {{ len(args["fail"]) }} alerts. Sent {{ len(args["done"]) }}'
alert-success:
condition: not args["fail"] # Send notification only if all alerts were successful
to: "admin@example.org"
subject: 'Sent all {{ len(args["done"]) }} alerts'
args['done']
is a list of dicts for sent emails. Each dict has keys:mail
: the email object that was sent. It has the following keys:to
: sendersubject
: email subjecthtml
: html contentbody
: body plain text contentattachments
: list of attachmentsimages
: dict of {image_name: path_to_image}cc
, bcc
, reply_to
, etcindex
: the index of the email sent. If each:
is used on a DataFrame or
dict, this is the keyrow
: the data used in the email to be sent. If each:
is used on a
DataFrame or dict, this is the valueargs['fail]
is a list of dicts for failed emails. This is identical to
args['done']
but has an additional key:error
: the Exception object that caused the failureTODO
If you have more than one email:
service set up, you can specific which email
service to use using service: name-of-email-service
.
You can preview and run alerts manually using the Admin Alert component at /admin/alert.
Note: Before Gramex 1.54, there was a mail app that you could import. That is now deprecated.
All emails sent via alerts are logged at $GRAMEXDATA/logs/alert.csv
and is
archived weekly by default. It stores these columns:
YYYY-mm-dd %H:%M:%SZ
The alert service takes four kinds of parameters:
years
: which year(s) to run on, e.g. “2018, 2019”. Default: *
months
: which month(s) to run on, e.g. “Jan-Mar,May”. Default: *
weekdays
: which weekdays(s) to run on, e.g. “Mon-Wed,Sat”. Default: *
dates
: which date(s) to run on, e.g. “10, 20, 30”. Default: *
hours
: which hour(s) to run at, e.g. “_/3” for every 3rd hour. Default: _
minutes
: which minute(s) to run at, e.g. “_” for every minute. Default: _
startup
: set this to true
to run the alert at startup. Set to *
to run
on startup and every time the configuration changes. Default: false
thread
: set this to true
to run in a separate thread. Default: false
to
, cc
, or bcc
must be specifiedservice
: name of the email service to use for sending emails.
Required, but if only one email service is defined, it is used.to
: a string with comma-separated emails or a list. Optionalcc
: like to:
but specifies the cc: addresses. Optionalbcc
: like to:
but specifies the bcc: addresses. Optionalfrom
: email ID of sender. This overrides the service:
from ID if possible. Optionalsubject
: subject template for the email. Optionalbody
: string for email text template. Optionalhtml
: string for email html template. Optionalbodyfile
: path to email body template file. Optionalhtmlfile
: path to email html template file. Optionalattachments
: attachments as a list of file paths or URLs. Optionalimages
: inline image attachments as {key: path}
. Link in HTML emails as <img src="cid:key">
. Optionaldata:
section are available as variables.
If data:
directly specifies a variable, it is stored in a data
variableconfig
holds the alert configuration – e.g. config.to
is the recipientrow
and index
contain the row values and index if each:
is useddata
: Optional data file or dict of datasets. All keys are available as
variables in the content templates and to condition
. Optional. Examples:data: {key: [...]}
– loads data in-placedata: {key: {url: file}}
– loads from a filedata: {key: {url: sqlalchemy-url, table: table}}
– loads from a databasedata: file
– same as data: {data: {url: file}}
data: {key: file}
– same as data: {key: {url: file}}
data: [...]
– same as data: {data: [...]}
each
: dataset name. Optional. It sends an email for each element of the
dataset. This adds 2 variables: index
and row
. If the dataset is a:index
and row
are the key and valueindex
and row
are the index and valueindex
and row
are the row index and DataFrame rowcondition
: an optional Python expression or pipeline that determines
whether to run the alert. All data:
keys are available to the expression. This may return a:once()
transform can be used. e.g. once('unique-key')
runs the alert
only once for every unique value of the arguments.data:
variablesdata['data']
To run an existing alert, run gramex.service.alert['your-alert-name'].run()
.
Replace 'your-alert-name'
with the name (key) of the alert you created.
For example:
import gramex
def functionhandler(handler):
# This triggers the alert called my-alert-name
gramex.service.alert['my-alert-name'].run()
You can call .run(args=...)
to pass an optional args
variable to the
templates. This can be used in the alert templates as
a variable args
. For example, this alert:
alert:
my-alert-name:
to: '{{ args.get('to', 'default@example.org') }}'
… will email default@example.org
. But when you run
gramex.service.alert(...).run(args={'to': 'hi@example.org'})
, it will email
hi@example.org
.
To create an alert programmatically use
gramex.services.create_alert(config)
.
This returns a function that sends an alert based on the configuration.
import yaml
import gramex.services
conf = {
'to': 'admin@example.org',
'subject': 'Alert from Gramex',
'body': 'This was sent from an API',
}
alert = gramex.services.create_alert('api-alert', conf)
kwargs = alert()
The returned kwargs
are the computed email contents.
Alerts can be used from the command line by running gramex mail
.
gramex mail
displays help about usagegramex mail <key>
sends mail named <key>
gramex mail --list
lists all keys in config filegramex mail --init
initializes config fileTo set it up:
gramex mail --init
. This prints the location of the config filetype:
to the email service type (e.g. gmail
, smtp
, etc.)email:
: is $GRAMEXMAILUSER
and password:
is $GRAMEXMAILPASSWORD
.
Set the environment variables GRAMEXMAILUSER
and GRAMEXMAILPASSWORD
and these will be used.To create an email, edit the config file and add an alert
like in a gramex.yaml
file.
To send the email, run gramex mail <key>
where <key>
is the alert key.
For example, with this configuration:
alert:
birthday-greeting:
to: john@example.org
subject: Happy birthday
body: Happy birthday John!
… the command gramex mail birthday-greeting
will send the email.