The Mail class wraps the /vis/mails API: assemble content from section objects or raw markdown, then render, test and send.
AI assisted, human approved — novem uses AI to review and keep our documentation up to date.
Before getting started, ensure you have a registered novem account and have installed the Python library — see installing the python library.
Note: Unlike other novem visuals, some e-mail instructions won't be sent to
the server until you explicitly call the render, test or send functions.
This is because e-mail has a separate render pipeline that's invoked on the server side and can take several seconds to render.
The Mail class can be used in two ways: by writing
novem markdown directly, or by composing
the mail from section objects. They produce the same result — the sections
are compiled into a markdown document at render time.
from novem import Mail
mail = Mail('name') # create mail object
mail.to = "user@example.com" # add recipient
mail.subject = "Test e-mail"
mail.content = """
{{ preview }}
Preview summary that is not visible in the main
body of the mail
{{ /preview }}
## Section title
---
Markdown text for text section
"""
mail.send()
from novem import Mail
from novem.mail import PreviewSection, MarkdownSection
mail = Mail('name') # create mail object
mail.to = "user@example.com" # add recipient
mail.subject = "Test e-mail"
mail.add_section(PreviewSection("""
Preview summary that is not visible in the main
body of the mail
"""))
mail.add_section(MarkdownSection("""
## Section title
---
Markdown text for text section
"""
))
mail.send()
As with all novem visuals, e-mails can be previewed in a browser by following
the url property of the mail object. If you've made changes you must call
render first to update the e-mail on the server.
from novem import Mail
mail = Mail('name') # create mail object
mail.url # https://novem.io/m/SHORTNAME
Note: While we've tried to match the e-mail web preview as close as possible to how it will look in your inbox, due to the differences in e-mail rendering between clients, there might be slight differences.
The Mail class maps to the /v1/vis/mails/ api end-point. Every attribute
below is a live property — assigning it writes to the corresponding api path —
and every attribute can also be supplied as a keyword argument to the
constructor. Properties flagged R trigger a full server-side render of the
e-mail (taking several seconds).
api path : rw : type : R : class property
-----------------:----:------:---:---------------
email_name : : : :
├── config : : : :
│ ├── theme : rw : str : R : theme
│ ├── template : rw : str : R : template
│ ├── size : rw : str : R : size
│ ├── reply_to : rw : bool : : reply_to
│ └── subject : rw : str : : subject
├── recipients : : : :
│ ├── to : rw : str : : to
│ ├── cc : rw : str : : cc
│ └── bcc : rw : str : : bcc
├── content : rw : str : R : content
├── status : rw : str : : status
├── log : r : str : : log
├── url : r : str : : url
├── shortname : r : str : : shortname
├── description : rw : str : : description
├── summary : rw : str : : summary
└── name : rw : str : : name
In addition there are python-only properties such as id, plus the
common options like
sharing.
| Attribute | Values | Notes |
|---|---|---|
theme | novem, novem-light, novem-dark | novem adapts to the reader's light/dark mode; the others don't. Custom themes on higher subscriptions. |
size | s / small, m / medium, l / large | Maximum content width: small targets mobile, large desktop. |
subject | string | The e-mail subject line. |
reply_to | bool | Adds a reply-to header with your registered, verified address. |
to cc bcc | string or list | See recipients below. |
log | read-only | Newline-delimited render log — first stop when a mail doesn't look right. |
shortname | read-only | The mail's shortname. |
url | read-only | The mail's web url. |
The to, cc and bcc properties accept novem usernames, e-mail addresses
and groups — as a list, or as a ;-separated string. See the
recipients reference
for address formats and tier restrictions.
from novem import Mail
# create a new mail
m = Mail("test_email",
to = "novem_demo", # set to and cc when creating the mail
cc = "novem_test"
)
# update recipients, both strings and lists work
m.to = ["novem_demo", "novem_test"]
# as well as usernames and email addresses
m.cc = "novem_research; demo@example.com"
render
This is an advanced feature of the python api, it's recommended that you ignore using this property and stick with
add_section, render and send unless you are confident that you understand the implications.
The entire user-controlled body of the e-mail is a markdown document behind
the content endpoint — writing it directly replaces whatever the sections
api would have produced. Like plot data, content can also be supplied by
invoking the mail object as a function.
from novem import Mail
# create a new mail
m = Mail("test_email")
with open("mail.md", "r") as f:
# set content directly
m.content = f
# or give content as the first argument
# of the class
m(f)
This is an advanced feature of the Python API. It is recommended to use the explicit send and test functions, as
they ensure the current segments are saved and the e-mail is updated before sending.
The status property holds the state of the mail: draft by default; writing
send or test triggers the corresponding delivery without re-rendering
first.
The three functions form one workflow:
render()compiles the added sections into a novem markdown document and writes it to thecontentendpoint, updating the web presentation and overwriting any existing content. With no sections added,render()is a no-op (assignm.content = ''if you want to clear a mail).test()callsrender(), then delivers the mail to your own verified address only — recipients are displayed but not delivered to — with the subject prefixedTEST:.send()callsrender(), then delivers to all valid recipients.
from novem import Mail
m = Mail("test_email")
m.test() # check your own inbox first
m.send() # then ship it
Novem sections are a convenience only, when render, test or
send is invoked the sections are compiled into a markdown document
that is then supplied to the API.
Sections are write only and not saved between Mail creations. You are
expected to recreate the Mail object and readd the sections if you want
to make changes.
Sections are rendered in the order they are added with add_section. Two of
them — markdown and code — are python-only conveniences; the rest are thin
wrappers whose constructor arguments map one-to-one onto the options of the
corresponding markdown section, with
underscores in place of spaces (include_title → include title). The
markdown reference is the authoritative description of every option:
| Class | Markdown section | Constructor |
|---|---|---|
MarkdownSection | (python only) | MarkdownSection(text) |
CodeSection | (python only) | CodeSection(text, lang=None) |
PreviewSection | preview | PreviewSection(text) |
ParagraphSection | paragraph | ParagraphSection(text, font_size=, font_style=, ...common) |
CalloutSection | callout | CalloutSection(text, type=, desc=, border=) |
VisSection | vis | VisSection(plot, width=, align=, include_title=, include_caption=, include_link=, override_title=, override_caption=) |
AuthorSection | author | AuthorSection(username, include_name=, include_bio=, include_picture=, override_bio=) |
AttachmentSection | attachment (docs pending) | AttachmentSection(vis, format=, name=) |
All wrapper sections also accept the
common parameters —
padding, margin, borders and colours — as keyword arguments (p, m, b,
fg, bg).
python only section
The e-mail body is novem markdown;
MarkdownSection is the building block for free-form text — titles,
paragraphs, lists. It takes no parameters except the markdown text.
from novem import Mail
from novem.mail import MarkdownSection
m = Mail("test_email")
m.add_section(MarkdownSection("""
# A title
Followed by a paragraph
* And
* a
* list
"""))
python only section
CodeSection adds syntax-highlighted code; the optional lang parameter
selects the language.
from novem import Mail
from novem.mail import CodeSection
m = Mail("test_email")
m.add_section(CodeSection("""
# Create and send a simple novem mail
from novem import Mail
Mail("test", to="me")("Hello, World!").send()
""",
lang="python", # specify language to highlight
))
unique
The preview section text is shown in
inbox list views instead of the e-mail body. There can only be one per mail —
the api uses the last one supplied, and the python library goes one step
further: add_section replaces any previously added preview, guaranteeing
uniqueness.
from novem import Mail
from novem.mail import PreviewSection
m = Mail("test_email")
m.add_section(PreviewSection("""
A short summary for the inbox list view.
"""))
from novem import Mail, Plot
from novem.mail import (
PreviewSection,
CalloutSection,
MarkdownSection,
VisSection,
AuthorSection,
)
# Create a new novem e-mail
m = Mail('novem_email_test',
to = 'me',
cc = '@novem_test',
subject = 'Daily test mail',
size = 'medium',
)
# Preview text — only shown in inbox list views, not the e-mail body
m.add_section(PreviewSection("""
Here comes the preview text, this is not visible in the body of the e-mail
"""))
# A highlighted callout
m.add_section(CalloutSection("""
Callout markdown content
""",
type="warning", # warning, error, info or success
desc="Description text",
border="dashed", # solid, dashed or None
))
# Some introductory text
m.add_section(MarkdownSection("""
## Simple markdown header
Some markdown text
"""))
# Embed a plot
m.add_section(VisSection(
Plot('/u/novem_demo/p/state_pop'),
include_title=True,
include_caption=True,
))
# Close with an author bio (name and picture are pulled from the profile)
m.add_section(AuthorSection("@novem_test",
override_bio="Novem Test is a novem test account.",
))
# Send a test to yourself, then to all recipients
m.test()
m.send()
- Mail quick start — the hands-on tutorial.
- Markdown reference — the content format and every section type.
- Recipients — address formats and tier restrictions.