docs: add some docs about sending app emails, and logging to email
This commit is contained in:
parent
fa76eb6aa9
commit
c1f3fcc412
|
@ -55,6 +55,7 @@ Contents
|
||||||
narr/config/index
|
narr/config/index
|
||||||
narr/db/index
|
narr/db/index
|
||||||
narr/cli/index
|
narr/cli/index
|
||||||
|
narr/email/index
|
||||||
narr/handlers/index
|
narr/handlers/index
|
||||||
narr/providers/index
|
narr/providers/index
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
Command Line Interface
|
Command Line
|
||||||
======================
|
============
|
||||||
|
|
||||||
Most apps will need some sort of command line usage, via cron or
|
Most apps will need some sort of command line usage, via cron or
|
||||||
otherwise. There are two main aspects to it:
|
otherwise. There are two main aspects to it:
|
||||||
|
|
113
docs/narr/email/custom.rst
Normal file
113
docs/narr/email/custom.rst
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
|
||||||
|
Custom Emails
|
||||||
|
=============
|
||||||
|
|
||||||
|
It is fairly straightforward to add a new type of email for your app
|
||||||
|
to send.
|
||||||
|
|
||||||
|
|
||||||
|
Configure Template Dir
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Your project should contain a folder dedicated to email templates;
|
||||||
|
these would be committed to your repo as for other project files.
|
||||||
|
This is often ``email/templates`` or ``templates/email`` under the
|
||||||
|
package root.
|
||||||
|
|
||||||
|
Also the app config must include this path for the email templates
|
||||||
|
setting. The order matters here as the first template found via
|
||||||
|
lookup will be used, for a given key (and content-type). To include
|
||||||
|
"Poser" as well as built-in WuttaWeb template dirs:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[wutta.email]
|
||||||
|
templates =
|
||||||
|
poser:templates/email
|
||||||
|
wuttaweb:email/templates
|
||||||
|
|
||||||
|
Often you can set this in your :term:`config extension` instead of
|
||||||
|
needing to set it in the config file::
|
||||||
|
|
||||||
|
from wuttjamaican.conf import WuttaConfigExtension
|
||||||
|
|
||||||
|
class PoserConfigExtension(WuttaConfigExtension):
|
||||||
|
|
||||||
|
def configure(self, config):
|
||||||
|
config.setdefault('wutta.email.templates',
|
||||||
|
'poser:templates/email wuttaweb:email/templates')
|
||||||
|
|
||||||
|
However the config file value, if set, will override the extension
|
||||||
|
default.
|
||||||
|
|
||||||
|
|
||||||
|
Create the Template
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Now that you have a configured template folder, create the template
|
||||||
|
file(s) within it. Each type of email is expected to have templates
|
||||||
|
for one or both of the ``text/plain`` and ``text/html`` content-types
|
||||||
|
(using ``txt`` and ``html`` as shorthand name, respectively).
|
||||||
|
|
||||||
|
Template files must use the :doc:`Mako template language <mako:index>`
|
||||||
|
and be named based on the
|
||||||
|
:attr:`~wuttjamaican.email.message.Message.key` for the email type, as
|
||||||
|
well as content-type.
|
||||||
|
|
||||||
|
Therefore a new email of type ``poser_alert_foo`` would need one or
|
||||||
|
both of these defined:
|
||||||
|
|
||||||
|
* ``poser_alert_foo.html.mako``
|
||||||
|
* ``poser_alert_foo.txt.mako``
|
||||||
|
|
||||||
|
It is generally a good idea to create both templates but for internal
|
||||||
|
emails, it is often sufficient to define only the HTML template. And
|
||||||
|
styles for email messages are notoriously wonky but again, for
|
||||||
|
internal use one need not worry about that so much.
|
||||||
|
|
||||||
|
Keep in mind, any context you wish to reference within the template,
|
||||||
|
must be provided by caller when sending email.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
At this time there are no built-in email templates for
|
||||||
|
WuttJamaican. However there is (at least) one template defined in
|
||||||
|
`wuttaweb:email/templates
|
||||||
|
<https://forgejo.wuttaproject.org/wutta/wuttaweb/src/branch/master/src/wuttaweb/email/templates>`_
|
||||||
|
which you can reference as a real example.
|
||||||
|
|
||||||
|
|
||||||
|
Configure Sending
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
With template file in the right place, your email can already be used.
|
||||||
|
However it would be sent only to the (app-wide) "default" recipients,
|
||||||
|
and with generic subject line.
|
||||||
|
|
||||||
|
To fix that you can add to your config file, again based on your email
|
||||||
|
key:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[wutta.email]
|
||||||
|
poser_alert_foo.subject = HIGH ALERT TYPE ${alert_type.upper()}
|
||||||
|
poser_alert_foo.sender = poser@example.com
|
||||||
|
poser_alert_foo.to = alert-monitor@example.com
|
||||||
|
poser_alert_foo.cc = admin@example.com
|
||||||
|
|
||||||
|
Note the subject line can be a Mako template string, referencing the
|
||||||
|
template context etc.
|
||||||
|
|
||||||
|
|
||||||
|
Test Sending
|
||||||
|
------------
|
||||||
|
|
||||||
|
Now you should be all set. When sending the email, you must provide
|
||||||
|
any context which may be needed for the template rendering. Assuming
|
||||||
|
you have that, call :meth:`~wuttjamaican.app.AppHandler.send_email()`
|
||||||
|
on your :term:`app handler`, giving it key and context::
|
||||||
|
|
||||||
|
app.send_email('poser_alert_foo', {
|
||||||
|
'alert_type': 'foo',
|
||||||
|
'alert_msg': "foo has unexpected value! or something happened, etc.",
|
||||||
|
})
|
18
docs/narr/email/index.rst
Normal file
18
docs/narr/email/index.rst
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
Email
|
||||||
|
=====
|
||||||
|
|
||||||
|
There is some built-in support for sending emails from your app. The
|
||||||
|
primary intended use case is for "internal" emails, e.g. sending daily
|
||||||
|
reports to the finance team etc. Each "type" of email can be sent to
|
||||||
|
different recipients.
|
||||||
|
|
||||||
|
You also may want to configure logging such that you receive email
|
||||||
|
when errors are logged.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
sending
|
||||||
|
custom
|
||||||
|
logging
|
107
docs/narr/email/logging.rst
Normal file
107
docs/narr/email/logging.rst
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
|
||||||
|
Logging to Email
|
||||||
|
================
|
||||||
|
|
||||||
|
It's possible to configure logging such that when "errors" are logged,
|
||||||
|
an email can be sent to some recipient(s).
|
||||||
|
|
||||||
|
You can set this up however you like of course; see upstream docs for
|
||||||
|
more info:
|
||||||
|
|
||||||
|
* :doc:`python:library/logging`
|
||||||
|
* :ref:`python:smtp-handler`
|
||||||
|
|
||||||
|
But the example shown below does as follows:
|
||||||
|
|
||||||
|
* root logger is DEBUG+ and uses 3 handlers: file, console, email
|
||||||
|
|
||||||
|
* file handler
|
||||||
|
|
||||||
|
* writes to ``app/log/wutta.log`` (you should specify absolute path instead)
|
||||||
|
|
||||||
|
* will auto-rotate log file when size reaches 10MB
|
||||||
|
|
||||||
|
* uses "generic" entry formatter
|
||||||
|
|
||||||
|
* console handler
|
||||||
|
|
||||||
|
* writes to STDERR for the current process
|
||||||
|
|
||||||
|
* writes only INFO+ entries (so no DEBUG)
|
||||||
|
|
||||||
|
* uses "console" entry formatter
|
||||||
|
|
||||||
|
* email handler
|
||||||
|
|
||||||
|
* writes only ERROR+ entries (so no DEBUG, INFO or WARNING)
|
||||||
|
|
||||||
|
* email is From: sender and To: recip(s) with Subject: as shown
|
||||||
|
|
||||||
|
* uses "generic" entry formatter (for message body)
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This will *not* send email when "uncaught exceptions" occur. This
|
||||||
|
will only send email when an error is *logged*. For example::
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
log.debug("do not email this")
|
||||||
|
log.info("nor this")
|
||||||
|
log.warning("nor this")
|
||||||
|
|
||||||
|
log.error("but *do* email this")
|
||||||
|
|
||||||
|
try:
|
||||||
|
raise RuntimeError
|
||||||
|
except:
|
||||||
|
log.exception("this also gets emailed")
|
||||||
|
|
||||||
|
# nb. no email is sent *here*, although possibly further up the
|
||||||
|
# stack another try/except block could be setup to log uncaught
|
||||||
|
# errors, in which case email may still be sent.
|
||||||
|
raise RuntimeError("this will just raise up the stack")
|
||||||
|
|
||||||
|
Now here is the example, which can be added to a normal :term:`config
|
||||||
|
file` (modifying as needed):
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[loggers]
|
||||||
|
keys = root
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = file, console, email
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic, console
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
handlers = file, console, email
|
||||||
|
level = DEBUG
|
||||||
|
|
||||||
|
[handler_file]
|
||||||
|
class = handlers.RotatingFileHandler
|
||||||
|
args = ('app/log/wutta.log', 'a', 1000000, 100, 'utf_8')
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
formatter = console
|
||||||
|
level = INFO
|
||||||
|
|
||||||
|
[handler_email]
|
||||||
|
class = handlers.SMTPHandler
|
||||||
|
args = ('localhost', 'poser@localhost', ['root@localhost', 'other@localhost'], "[Poser] Logging")
|
||||||
|
formatter = generic
|
||||||
|
level = ERROR
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(funcName)s: %(message)s
|
||||||
|
datefmt = %Y-%m-%d %H:%M:%S
|
||||||
|
|
||||||
|
[formatter_console]
|
||||||
|
format = %(levelname)-5.5s [%(name)s][%(threadName)s] %(funcName)s: %(message)s
|
96
docs/narr/email/sending.rst
Normal file
96
docs/narr/email/sending.rst
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
|
||||||
|
Sending Email
|
||||||
|
=============
|
||||||
|
|
||||||
|
Here we'll describe enough to get started sending email.
|
||||||
|
|
||||||
|
|
||||||
|
Basics
|
||||||
|
------
|
||||||
|
|
||||||
|
To send an email you (usually) need 3 things:
|
||||||
|
|
||||||
|
* key - unique key identifying the type of email
|
||||||
|
* template - template file to render message body
|
||||||
|
* context - context dict for template file rendering
|
||||||
|
|
||||||
|
And actually the template just needs to exist somewhere it can be
|
||||||
|
found, but when calling
|
||||||
|
:meth:`~wuttjamaican.app.AppHandler.send_email()` you only need to
|
||||||
|
give the key and context::
|
||||||
|
|
||||||
|
app.send_email('poser_alert_foo', {
|
||||||
|
'alert_type': 'foo',
|
||||||
|
'alert_msg': "foo has unexpected value! or something happened, etc.",
|
||||||
|
})
|
||||||
|
|
||||||
|
In that example ``alert_type`` and ``alert_msg`` are the context, and
|
||||||
|
the template file(s) may display either/both however it wants.
|
||||||
|
|
||||||
|
If you do not provide all the needed context, you will likely get a
|
||||||
|
template rendering error. The only way to know for sure which context
|
||||||
|
data is needed, is to look at the template file itself.
|
||||||
|
|
||||||
|
|
||||||
|
Email Discovery
|
||||||
|
---------------
|
||||||
|
|
||||||
|
So how does the above work, e.g. how did it find the template? And
|
||||||
|
how can you know which are the "possible" email types you can send?
|
||||||
|
|
||||||
|
This is covered in more detail in :doc:`custom` but for now we'll
|
||||||
|
just say:
|
||||||
|
|
||||||
|
The template folder(s) must be configured, but otherwise "any email
|
||||||
|
type key" may be used. As long as the template(s) is found, the email
|
||||||
|
can be sent - albeit to global default recipients, unless that is
|
||||||
|
further configured for the email type.
|
||||||
|
|
||||||
|
In other words there is no "central registry" of the possible email
|
||||||
|
types, per se. In practice the list of template files, found within
|
||||||
|
configured template folders, is effectively the list of possible email
|
||||||
|
types. (There is no "default template" for sending.)
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
At this time there are no built-in email templates for
|
||||||
|
WuttJamaican. However there is (at least) one template defined in
|
||||||
|
`wuttaweb:email/templates
|
||||||
|
<https://forgejo.wuttaproject.org/wutta/wuttaweb/src/branch/master/src/wuttaweb/email/templates>`_.
|
||||||
|
|
||||||
|
|
||||||
|
Email Delivery
|
||||||
|
--------------
|
||||||
|
|
||||||
|
If the email template can be found and rendered, it's time to "really"
|
||||||
|
send the email. How does that work?
|
||||||
|
|
||||||
|
Various message headers may be specified by caller, but usually they
|
||||||
|
will be auto-obtained from config. This includes the sender and
|
||||||
|
recipients, and subject line. If neither specifies anything regarding
|
||||||
|
the current email type, fallback "default" values are used (assuming
|
||||||
|
those are configured). This again is explained further in
|
||||||
|
:doc:`custom`.
|
||||||
|
|
||||||
|
So we have a complete message with all headers; the final step is to
|
||||||
|
send this via SMTP. While technically this supports sending to an
|
||||||
|
"external" SMTP server, the suggested use case is to always send to
|
||||||
|
localhost; to minimize lag and give full flexibility. (If sending to
|
||||||
|
localhost you should not need any specific config for that.)
|
||||||
|
|
||||||
|
In any case here is sample config if you were to use an external
|
||||||
|
SMTP server:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[wutta.mail]
|
||||||
|
smtp.server = smtp.example.com
|
||||||
|
smtp.username = mailuser
|
||||||
|
smtp.password = mailpass
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
As of now, TLS is not supported! Which is because of the preferred
|
||||||
|
use of localhost as SMTP server. Obviously the local MTA software
|
||||||
|
(e.g. Postfix) can then send via another relay, and it should
|
||||||
|
probably use TLS for that.
|
Loading…
Reference in a new issue