The Mail class wraps the /vis/mails API: assemble content from section objects or raw markdown, then render, test and send.
AI-assisted, human-approvednovem 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.
Two styles
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.
Load Markdown from a file
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()
Compose content with section objects
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()
Viewing your mail
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: E-mail clients can render the same content differently. Send a test to check the result in the clients your recipients use.
The Mail class
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
connection settings.
Attributes
| 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. |
Recipients
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"
Content
renderWriting content replaces the complete e-mail body. If you also use
add_section, a later render() replaces it with the compiled sections.
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)
Status
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.
render, test and send
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
Sections
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 that emit the corresponding
Markdown block. Each constructor
argument becomes one config key, underscore replaced by a space
(include_bio → include bio). The markdown reference is the authoritative
description of every option, and the grammar has moved on since these
wrappers were written: some arguments land on a key the grammar has renamed
(accepted with a deprecation warning), and a few have no markdown key at all.
The table lists the constructors; the notes below say which is which.
| 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=, cborder=) |
VisSection | vis | VisSection(plot, width=, align=, include_title=, include_caption=, include_link=, override_title=, override_caption=) |
AuthorSection | author | AuthorSection(username, include_bio=, include_picture=, override_bio=) |
AttachmentSection | attachment | AttachmentSection(vis, format=, name=) |
How the arguments land in the grammar:
ParagraphSection—font_sizeandfont_styleare live aliases of the paragraph'sszandstkeys.CalloutSection—typemaps directly.descis emitted asdesc:, which the grammar accepts and rewrites totitle:with adeprecated_keywarning; writetitle:in hand-authored markdown.cborderhas no markdown counterpart: the callout'sborderkey is a boolean toggle, andcborder:is dropped as an unknown key.VisSection—width,alignandinclude_linkmap towidth,alignandinclude link.include_titleandinclude_captionare emitted asinclude title/include caption, which the grammar rewrites totitle:/caption:with adeprecated_keywarning.override_titleandoverride_captionhave no markdown counterpart and are dropped as unknown keys; in markdown, a string value does the override (title: "Custom title").AuthorSection—include_bio,include_pictureandoverride_biomap toinclude bio,include pictureandoverride bio, all live keys.
All wrapper sections also accept the
styling keys —
padding, margin, borders and colours — as keyword arguments (p, m, b,
fg, bg).
Markdown
python only sectionThe 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
"""))
Code
python only sectionCodeSection 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
))
Preview
uniqueThe preview block 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.
"""))
Complete example
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", # emitted as the deprecated `desc:`, rendered as the callout title
))
# Some introductory text
m.add_section(MarkdownSection("""
## Simple markdown header
Some markdown text
"""))
# Embed a plot. The python library still emits `include title` /
# `include caption`; the grammar accepts them as deprecated spellings of
# `title` / `caption`.
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()
See also
- Mail quick start — the hands-on tutorial.
- Markdown reference — the content format and supported blocks.
- Recipients — address formats and tier restrictions.