Libraries

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

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.

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

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_titleinclude title). The markdown reference is the authoritative description of every option:

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=, border=)
VisSectionvisVisSection(plot, width=, align=, include_title=, include_caption=, include_link=, override_title=, override_caption=)
AuthorSectionauthorAuthorSection(username, include_name=, include_bio=, include_picture=, override_bio=)
AttachmentSectionattachment (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()