Skip to content

Mail

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

AttributeValuesNotes
themenovem, novem-light, novem-darknovem adapts to the reader's light/dark mode; the others don't. Custom themes on higher subscriptions.
sizes / small, m / medium, l / largeMaximum content width: small targets mobile, large desktop.
subjectstringThe e-mail subject line.
reply_toboolAdds a reply-to header with your registered, verified address.
to cc bccstring or listSee recipients below.
logread-onlyNewline-delimited render log — first stop when a mail doesn't look right.
shortnameread-onlyThe mail's shortname.
urlread-onlyThe 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

render

Writing 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 the content endpoint, updating the web presentation and overwriting any existing content. With no sections added, render() is a no-op (assign m.content = '' if you want to clear a mail).
  • test() calls render(), then delivers the mail to your own verified address only — recipients are displayed but not delivered to — with the subject prefixed TEST: .
  • send() calls render(), 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.

ClassMarkdown sectionConstructor
MarkdownSection(python only)MarkdownSection(text)
CodeSection(python only)CodeSection(text, lang=None)
PreviewSectionpreviewPreviewSection(text)
ParagraphSectionparagraphParagraphSection(text, font_size=, font_style=, ...common)
CalloutSectioncalloutCalloutSection(text, type=, desc=, cborder=)
VisSectionvisVisSection(plot, width=, align=, include_title=, include_caption=, include_link=, override_title=, override_caption=)
AuthorSectionauthorAuthorSection(username, include_bio=, include_picture=, override_bio=)
AttachmentSectionattachmentAttachmentSection(vis, format=, name=)

How the arguments land in the grammar:

  • ParagraphSection — font_size and font_style are live aliases of the paragraph's sz and st keys.
  • CalloutSection — type maps directly. desc is emitted as desc:, which the grammar accepts and rewrites to title: with a deprecated_key warning; write title: in hand-authored markdown. cborder has no markdown counterpart: the callout's border key is a boolean toggle, and cborder: is dropped as an unknown key.
  • VisSection — width, align and include_link map to width, align and include link. include_title and include_caption are emitted as include title / include caption, which the grammar rewrites to title: / caption: with a deprecated_key warning. override_title and override_caption have no markdown counterpart and are dropped as unknown keys; in markdown, a string value does the override (title: "Custom title").
  • AuthorSection — include_bio, include_picture and override_bio map to include bio, include picture and override 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 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

"""))

Code

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
))

Preview

unique

The 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