Osy#betaRun it on Osyrin: osyrin.com

Kits · Mail

Email templates

Mail that looks designed — a layout with preview line, logo and footer, headings, text, buttons, pictures and receipt lines, in the app's own theme — and the mail blocks staff arrange in a mail's region.

Package
Osysharp.EmailTemplates
Install
use Osysharp.EmailTemplates@0;
Group
Mail
Tests
15 in the kit’s own suite
Builds on
—
Used by
Shop, Mailing lists, Accounts, Invitations, Subscriptions, Notifications, Feedback — how did we do?

Use case

Your app sends mail about something a person did: an order confirmation, a shipping notice, a refund, a code to redeem, a reminder. This package makes that mail look like it came from a designed product instead of a script — a branded page with a clear headline, the lines of a receipt with their amounts, one obvious button, and a footer with the sender's address — and it fills it from your data.

It renders on the server, in the app's own theme, into HTML every mail client lays out (tables, every style inline, no script, no web font) and a plain-text twin that says the same thing. It formats mail; it does not send it — hand the result to whatever sends your mail (Osysharp.Resend, or any provider).

A receipt rendered with Osysharp.EmailTemplates
A receipt rendered with Osysharp.EmailTemplates

A shop's receipt, photographed by osy test --pixels from this package's own tests.

Install and use

osy

app Shop {
  use Osysharp.EmailTemplates@0;
  model "model/**/*.osy";
}

A mail is an [Email] component with a subject =>. Calling it from any function — a workflow step included — answers a RenderedEmail with Subject, Html and Text:

osy

using Osysharp.EmailTemplates;

[Email]
public component OrderConfirmation(Order order) {
  subject => $"Your order {order.Reference}";
  render {
    EmailLayout("Grounds & Grapes", preview: $"Order {order.Reference} is reserved for you",
                address: "Storgatan 1, 111 22 Stockholm") {
      EmailHeading($"Thank you, {order.CustomerName}");
      EmailText("Your order is reserved and on its way to being packed.");
      EmailTable() {
        foreach (var line in order.Lines) {
          EmailRow($"{line.Quantity} × {line.Name}", Money(line.Price * line.Quantity), detail: line.Size);
        }
        EmailTotal("Total", Money(order.Total));
      }
      EmailButton("See your order", $"https://shop.example.com/orders/{order.Reference}");
    }
  }
}

void SendConfirmation(Order order) {
  var mail = OrderConfirmation(order);
  new ResendMailer { ApiKey = Secret.ResendApiKey, From = "Shop <[email protected]>" }.Send(new EmailMessage {
    To = [order.Email], Subject = mail.Subject, Html = mail.Html, Text = mail.Text,
  });
}

What it ships

partwhat it draws
EmailLayout(brand, preview?, logo?, address?, unsubscribe?, footer?) { … }the frame: the app's own page in a 600-pixel column, with the brand (or its logo) at the top, the mail inside, and a footer with the sender's address and — for a mail a reader signed up to — an unsubscribe link
EmailHeading(text)the headline, in the theme's display face
EmailText(text)a paragraph; a line break is kept
EmailNote(text)small print in the muted colour
EmailButton(label, href)the one thing to do next — a button that is a link in every client, Outlook included
EmailImage(src, alt, width?)a picture by address; a path is sent as the app's absolute address
EmailCode(code)a code to type or read out — a gift code, a return code — large and spaced
EmailDivider()a rule
EmailTable() { … }a receipt's lines — EmailRows and an EmailTotal
EmailRow(label, value, detail?)one line: what it is (and a detail under it) and what it costs
EmailTotal(label, value)the line a receipt ends on, bold, under a rule

Every colour, size and face is a theme token (Colors.Primary, FontSize.Title, Radius.Md), and the renderer writes the app's value for each into the mail — so a shop whose brand is orange sends orange buttons.

The mail blocks — what staff arrange

A mail's words that staff own are a Region of blocks (use Osysharp.Blocks; in the app), arranged at the page desk (Osysharp.Pages) like a page's — a draft, published, in each language, with the mail itself previewed beside it. The kit ships the everyday blocks, each an [Email] part marked [Block] whose words fill the mail's {placeholders}:

blockstaff fill in
EmailWords — Wordsa paragraph in Markdown
EmailHeadline — Heading and wordsa heading and a paragraph under it
EmailPicture — Picturea photograph (one the app ships, or an upload) and what it shows
EmailAction — Buttonwhat it says and where it goes
EmailQuote — Quotewhat somebody said, who, and one of the theme's grounds
EmailSmallPrint — Small printa quiet line at the foot

osy

[Email]
public component Welcome(Member member) {
  subject => $"Welcome, {member.Name}";
  placeholders { member.name => member.Name; }
  render {
    EmailLayout("The Rowing Club") {
      Region("welcome", allow: [EmailWords, EmailPicture, EmailQuote], sample: WelcomeSample) {
        EmailWords(words: "Welcome aboard, **{member.name}**.");
      }
    }
  }
}

An app that arranges nothing uses them as ordinary parts and needs no Osysharp.Blocks.

Make it yours

Each part is an ordinary [Email] component, so a different button is a component of your own written the same way, over the platform's email atoms (Stack, Row, Box, Text, Link, Image, Divider, Spacer, Markdown, Preview). osy get EmailButton copies this one into your app to start from.

The reference: osy docs ui-email (the [Email] render target) and osy docs ui-email-templates-kit (this package).

Source

model/Parts.osy (the parts) and model/Blocks.osy (the mail blocks) — two files of Osy#.

Get it

Add the kit to your app’s manifest:

osy

app MyApp {
  use Osysharp.EmailTemplates@0;
}

Then run osy lock: it downloads the release, verifies it by its content address and pins that version in osyrin.lock. osy kits Osysharp.EmailTemplates shows what the kit is for, from inside your app.

Reference: osy docs ui-email-templates-kit.