# email

> Compose email with recipients, HTML bodies and attachments, check it, and send it through an SMTP server.

This is the Markdown version of https://osl-lang.org/packages/email. Every page of the OSL docs has one: add .md to its path. https://osl-lang.org/llms.txt lists them all.

```osl
import "std:email"
```

`std:email` builds an email message and sends it through an SMTP server. Composing needs no network, so you can build, check and print a message to see exactly what would go out. Only `send` talks to a server.

## Build a message

`email.new(options)` returns a result, because it checks every address. Add recipients, set a body, and read the pieces back:

```osl
import "std:email"

auto mail = email.new({from: "ada@example.com", to: "bob@example.com", subject: "Lunch?"}).unwrap()
mail.setBody("Hi Bob,\nare you free at noon?")

log mail.from() mail.to() mail.subject()
log mail.preview()
```

Output:

```text
ada@example.com ["bob@example.com"] Lunch?
From: ada@example.com
To: bob@example.com
Subject: Lunch?

Hi Bob,
are you free at noon?
```

`preview()` is a quick look for logs and tests.

## More recipients

`addTo`, `addCc` and `addBcc` add one address each, and `setTo` and friends replace the lot. Bcc addresses get the message but never appear in the headers:

```osl
import "std:email"

auto mail = email.new({from: "ada@example.com", to: ["bob@example.com", "cy@example.com"]}).unwrap()
mail.addCc("dee@example.com").unwrap()
mail.addBcc("boss@example.com").unwrap()

log mail.recipients()
log mail.bcc()
log mail.hasRecipient("boss@example.com")
log mail.stringify().contains("boss@example.com")
```

Output:

```text
["bob@example.com","cy@example.com","dee@example.com","boss@example.com"]
["boss@example.com"]
true
false
```

## Bad addresses

Adding a bad address fails and leaves the message as it was:

```osl
import "std:email"

auto mail = email.new({from: "ada@example.com"}).unwrap()

auto bad = mail.addTo("not an address")
log bad.isErr() bad.unwrapErr().kind
log mail.to()

log email.new({from: "nope"}).isErr()
```

Output:

```text
true ValueError
[]
true
```

## Check before sending

`validate()` says whether the message can be sent: it needs a sender, a recipient and a subject, and it explains what is missing:

```osl
import "std:email"

auto mail = email.new({to: "bob@example.com"}).unwrap()
log mail.validate().unwrapErr().message

mail.setFrom("ada@example.com").unwrap()
log mail.validate().unwrapErr().message

mail.setSubject("Hi")
mail.setBody("hello")
log mail.validate().isOk()
```

Output:

```text
email.Email.validate: the message has no sender; call setFrom first
email.Email.validate: the message has no subject; call setSubject first
true
```

## HTML and attachments

`setBody(text, {html: true})` sends the body as HTML. `attach(name, data)` adds a file from memory and `attachFile(path)` one from disk:

```osl
import "std:email"

auto mail = email.new({from: "ada@example.com", to: "bob@example.com", subject: "Report"}).unwrap()
mail.setBody("<h1>Weekly report</h1><p>See attached.</p>", {html: true})
mail.attach("numbers.csv", "week,sales\n1,10\n2,14\n").unwrap()

log mail.isHTML()
log mail.attachments()
string wire = mail.stringify()
log wire.contains("Content-Type: text/html")
log wire.contains("numbers.csv")
```

Output:

```text
true
["numbers.csv"]
true
true
```

`stringify()` returns the complete message as `send` would transmit it, which is useful for tests that check what an app would email.

**Quiz.** Which recipients appear in the message's headers?

- [x] To and Cc.
- [ ] To, Cc and Bcc.
- [ ] Only To.

Answer: Bcc recipients are delivered to but never listed, so the others can't see them.

## Sending

`mail.send(host, options)` delivers the message. The defaults suit a normal provider: port 587 with TLS negotiated when the server offers it. Credentials go in the options:

```osl
import "std:email"
import "std:env"

auto mail = email.new({from: "ada@example.com", to: "bob@example.com", subject: "Hi"}).unwrap()
mail.setBody("Sent from OSL")

auto outcome = mail.send("smtp.example.com", {
  username: env.get("SMTP_USER"),
  password: env.get("SMTP_PASSWORD")
})
switch outcome (
  case ok(_)
    log "sent"
  case err(e)
    log "not sent:" e.kind e.message
)
```

Failures come back as an error: `NetworkError` and `TimeoutError` for connection trouble, `PermissionError` for a refused login, `UnsupportedError` when the server has no TLS and you gave a password (the password would travel in the clear), `RuntimeError` when the server refuses the sender, recipient or message, and `StateError` when the message isn't valid. Read the password from [env](/packages/env.md), not from source.

### Exercise: A receipt

Build a message from `shop@example.com` to a customer with the subject `Your receipt` and a body of `Total: 12.50`. Print its subject, and whether it is valid.

Starting code:

```osl
import "std:email"

auto mail = email.new({}).unwrap()

log mail.subject()
log mail.validate().isOk()
```

It should print:

```text
Your receipt
true
```

Hint 1: Pass `from`, `to` and `subject` to `email.new`.

Hint 2: `mail.setBody("Total: 12.50")` sets the body.

A solution:

```osl
import "std:email"

auto mail = email.new({
  from: "shop@example.com",
  to: "ada@example.com",
  subject: "Your receipt"
}).unwrap()
mail.setBody("Total: 12.50")

log mail.subject()
log mail.validate().isOk()
```

## API reference

## API reference

Every member of `std:email`, generated from the source of OSL.

### Functions

#### `email.addBcc(any recipient) boolean`

**Deprecated.** Deprecated: use email.new() and message.addBcc(address) instead.

addBcc adds a Bcc address to the shared message.

#### `email.addCc(any recipient) boolean`

**Deprecated.** Deprecated: use email.new() and message.addCc(address) instead.

addCc adds a Cc address to the shared message.

#### `email.addTo(any recipient) boolean`

**Deprecated.** Deprecated: use email.new() and message.addTo(address) instead.

addTo adds a To address to the shared message.

#### `email.addToMany(array recipients) boolean`

**Deprecated.** Deprecated: use email.new({to: addresses}) instead.

addToMany adds To addresses to the shared message.

#### `email.attachContent(object files) boolean`

**Deprecated.** Deprecated: use email.new() and message.attach(name, data) instead.

attachContent attaches name → content pairs to the shared message.

#### `email.attachFile(any filePath) boolean`

**Deprecated.** Deprecated: use email.new() and message.attachFile(path) instead.

attachFile attaches a file to the shared message.

#### `email.buildMessage(string from, any ...unused) string`

**Deprecated.** Deprecated: use message.stringify() instead.

buildMessage returns the shared message's text as sent by from.

#### `email.clear()`

**Deprecated.** Deprecated: use message.clear() instead.

clear empties the shared message except its sender.

#### `email.create() *email.Email`

**Deprecated.** Deprecated: use email.new(options?) instead.

create returns a new, empty message.

#### `email.fromMap(object data) *email.Email`

**Deprecated.** Deprecated: use email.new(options) instead, which fails with ValueError for an invalid address.

fromMap builds a message from {from, to, cc, bcc, subject, body, html},
silently skipping invalid addresses.

#### `email.getBcc() array`

**Deprecated.** Deprecated: use email.new() and message.bcc() instead.

getBcc returns the shared message's Bcc addresses.

#### `email.getBody() string`

**Deprecated.** Deprecated: use email.new() and message.body() instead.

getBody returns the shared message's body.

#### `email.getCc() array`

**Deprecated.** Deprecated: use email.new() and message.cc() instead.

getCc returns the shared message's Cc addresses.

#### `email.getHeaders() string[string]`

**Deprecated.** Deprecated: use message.headers() instead.

getHeaders returns the shared message's From, To, Subject and Date.

#### `email.getRecipientCount() int`

**Deprecated.** Deprecated: use message.recipients().len instead.

getRecipientCount returns the shared message's number of recipients.

#### `email.getRecipients() array`

**Deprecated.** Deprecated: use message.recipients() instead.

getRecipients returns the shared message's recipients.

#### `email.getSubject() string`

**Deprecated.** Deprecated: use email.new() and message.subject() instead.

getSubject returns the shared message's subject.

#### `email.getTo() array`

**Deprecated.** Deprecated: use email.new() and message.to() instead.

getTo returns the shared message's To addresses.

#### `email.hasRecipient(string recipient) boolean`

**Deprecated.** Deprecated: use message.hasRecipient(address) instead.

hasRecipient reports whether the shared message has the recipient.

#### `email.isHTML() boolean`

**Deprecated.** Deprecated: use email.new() and message.isHTML() instead.

isHTML reports whether the shared message's body is HTML.

#### `email.new(object? options) result<*email.Email, error>`

new returns a message. Options: from, to, cc and bcc (an address or an
array of addresses), subject, body and html (true when body is HTML).
Fails with ValueError when an address is invalid; addresses must be plain
ASCII such as "name@example.com".

#### `email.preview() string`

**Deprecated.** Deprecated: use message.preview() instead.

preview summarises the shared message.

#### `email.queue() object`

**Deprecated.** Deprecated: use message.toMap() instead.

queue returns {email, queuedAt, sent: false} for the shared message.

#### `email.reset()`

**Deprecated.** Deprecated: use message.reset() instead.

reset empties the shared message.

#### `email.sendGmail(any username, any password) object`

**Deprecated.** Deprecated: use message.send("smtp.gmail.com", {username, password}) instead.

sendGmail sends the shared message through Gmail.

#### `email.sendLocalhost() object`

**Deprecated.** Deprecated: use message.send("localhost", {port: 25}) instead.

sendLocalhost sends the shared message through localhost:25.

#### `email.sendOffice365(any username, any password) object`

**Deprecated.** Deprecated: use message.send("smtp.office365.com", {username, password}) instead.

sendOffice365 sends the shared message through Office 365.

#### `email.sendOutlook(any username, any password) object`

**Deprecated.** Deprecated: use message.send("smtp-mail.outlook.com", {username, password}) instead.

sendOutlook sends the shared message through Outlook.

#### `email.sendWithSmtp(any host, any port, any username, any ...rest) object`

**Deprecated.** Deprecated: use email.new() and message.send(host, options) instead.

sendWithSmtp(host, port, username, password, auth) sends the shared
message.

#### `email.setBcc(any recipients) boolean`

**Deprecated.** Deprecated: use email.new() and message.setBcc(addresses) instead.

setBcc replaces the shared message's Bcc addresses, skipping invalid
ones.

#### `email.setBody(any body)`

**Deprecated.** Deprecated: use email.new({body}) instead.

setBody sets the shared message's body.

#### `email.setCc(any recipients) boolean`

**Deprecated.** Deprecated: use email.new() and message.setCc(addresses) instead.

setCc replaces the shared message's Cc addresses, skipping invalid ones.

#### `email.setFrom(any addr) boolean`

**Deprecated.** Deprecated: use email.new({from}) and message.setFrom(address) instead.

setFrom sets the shared message's sender, or returns false for an
invalid address.

#### `email.setHTML(any html) boolean`

**Deprecated.** Deprecated: use email.new({body, html: true}) instead.

setHTML sets an HTML body on the shared message and returns true.

#### `email.setSubject(any subject)`

**Deprecated.** Deprecated: use email.new({subject}) instead.

setSubject sets the shared message's subject.

#### `email.setText(any text)`

**Deprecated.** Deprecated: use email.new({body}) instead.

setText sets a plain-text body on the shared message.

#### `email.setTo(any recipients) boolean`

**Deprecated.** Deprecated: use email.new() and message.setTo(addresses) instead.

setTo replaces the shared message's To addresses.

#### `email.toMap() object`

**Deprecated.** Deprecated: use message.toMap() instead.

toMap returns the shared message as an object.

#### `email.validate() boolean`

**Deprecated.** Deprecated: use message.validate() instead.

validate reports whether the shared message has a sender, a To address
and a subject.

#### `email.wrapText(any width) boolean`

**Deprecated.** Deprecated: use message.setBody(text, {wrap: width}) instead.

wrapText wraps the shared message's body.

### `*email.Email`

#### `email.addBcc(string address) result<unit, error>`

addBcc adds a Bcc address, like addTo.

#### `email.addCc(string address) result<unit, error>`

addCc adds a Cc address, like addTo.

#### `email.addTo(string address) result<unit, error>`

addTo adds a To address. An address already on the message (in any
list, ignoring case) is skipped. Fails with ValueError when address is
invalid.

#### `email.addToMany(array recipients) boolean`

**Deprecated.** Deprecated: use message.addTo(address) for each address instead.

addToMany adds To addresses, stopping with false at the first invalid or
duplicate one.

#### `email.attach(string name, any data) result<unit, error>`

attach adds an attachment called name holding data, a string or byte[].
Fails with ValueError when name is empty or the message already has an
attachment with that name. Throws TypeError for other data types.

#### `email.attachContent(object files) boolean`

**Deprecated.** Deprecated: use message.attach(name, data) instead.

attachContent attaches each name → content pair of files, returning
false at the first empty or duplicate name.

#### `email.attachFile(string path, object? options) result<unit, error>`

attachFile attaches the file at path, named after the file. Options:
name (the attachment's name instead). Fails with NotFoundError or
PermissionError when the file can't be read, IOError when path is a
directory, and ValueError when an attachment with that name exists.

#### `email.attachments() string[]`

attachments returns the attachment names, sorted.

#### `email.bcc() string[]`

bcc returns the Bcc addresses. They receive the message but are never
written into it.

#### `email.body() string`

body returns the body text.

#### `email.buildMessage(string from, any ...unused) string`

**Deprecated.** Deprecated: use message.stringify() instead.

buildMessage returns the message text as sent by from.

#### `email.cc() string[]`

cc returns the Cc addresses.

#### `email.clear()`

clear removes the recipients, subject, body and attachments, keeping the
sender.

#### `email.create() *email.Email`

**Deprecated.** Deprecated: use email.new(options?) instead.

create returns a new, empty message.

#### `email.from() string?`

from returns the sender's address, or null when none is set.

#### `email.fromMap(object data) *email.Email`

**Deprecated.** Deprecated: use email.new(options) instead.

fromMap builds a new message from {from, to, cc, bcc, subject, body,
html}, silently skipping invalid addresses.

#### `email.getBcc() array`

**Deprecated.** Deprecated: use message.bcc() instead.

getBcc returns the Bcc addresses.

#### `email.getBody() string`

**Deprecated.** Deprecated: use message.body() instead.

getBody returns the body.

#### `email.getCc() array`

**Deprecated.** Deprecated: use message.cc() instead.

getCc returns the Cc addresses.

#### `email.getHeaders() string[string]`

**Deprecated.** Deprecated: use message.headers() instead.

getHeaders returns From, To, Subject and Date.

#### `email.getRecipientCount() int`

**Deprecated.** Deprecated: use message.recipients().len instead.

getRecipientCount returns the number of recipients.

#### `email.getRecipients() array`

**Deprecated.** Deprecated: use message.recipients() instead.

getRecipients returns every recipient.

#### `email.getSubject() string`

**Deprecated.** Deprecated: use message.subject() instead.

getSubject returns the subject.

#### `email.getTo() array`

**Deprecated.** Deprecated: use message.to() instead.

getTo returns the To addresses.

#### `email.hasRecipient(string address) boolean`

hasRecipient reports whether address is a To, Cc or Bcc recipient,
ignoring case.

#### `email.headers() string[string]`

headers returns the headers the message is sent with: From, To, Cc (when
there are Cc addresses), Subject, Date (now) and MIME-Version. Bcc
addresses are never included.

#### `email.isHTML() boolean`

isHTML reports whether the body is sent as HTML rather than plain text.

#### `email.preview() string`

preview returns a short readable summary: the From, To and Subject lines,
a blank line and the body.

#### `email.queue() object`

**Deprecated.** Deprecated: use message.toMap() instead.

queue returns {email, queuedAt (unix seconds), sent: false}. Nothing is
queued.

#### `email.recipients() string[]`

recipients returns every To, Cc and Bcc address, in that order.

#### `email.reset()`

reset clears everything, the sender included.

#### `email.send(string host, object? options) result<unit, error>`

send delivers the message through the SMTP server at host. Options: port
(default 587, or 465 with implicit TLS), username, password, tls ("auto",
"starttls", "implicit" or "none") and timeout (milliseconds, default
30000). Fails with StateError, PermissionError, UnsupportedError,
NetworkError, TimeoutError or RuntimeError.

#### `email.sendGmail(any username, any password) object`

**Deprecated.** Deprecated: use message.send("smtp.gmail.com", {username, password}) instead.

sendGmail sends through smtp.gmail.com:587.

#### `email.sendLocalhost() object`

**Deprecated.** Deprecated: use message.send("localhost", {port: 25}) instead.

sendLocalhost sends through localhost:25 without signing in.

#### `email.sendOffice365(any username, any password) object`

**Deprecated.** Deprecated: use message.send("smtp.office365.com", {username, password}) instead.

sendOffice365 sends through smtp.office365.com:587.

#### `email.sendOutlook(any username, any password) object`

**Deprecated.** Deprecated: use message.send("smtp-mail.outlook.com", {username, password}) instead.

sendOutlook sends through smtp-mail.outlook.com:587.

#### `email.sendWithSmtp(any host, any port, any username, any ...rest) object`

**Deprecated.** Deprecated: use message.send(host, {port, username, password}) instead, which fails with a typed error.

sendWithSmtp(host, port, username, password, auth) sends the message and
returns {success: true, message} or {success: false, error}.

#### `email.setBcc(array addresses) result<unit, error>`

setBcc replaces the Bcc addresses, like setTo.

#### `email.setBody(string body, object? options)`

setBody replaces the body. Options: html (true for an HTML body, false
for plain text; when omitted the format stays as it was, plain text for
a new message) and wrap (wrap each line at this many characters, breaking
between words). Throws ValueError when wrap is less than 1.

#### `email.setCc(array addresses) result<unit, error>`

setCc replaces the Cc addresses, like setTo.

#### `email.setFrom(string address) result<unit, error>`

setFrom sets the sender's address. Fails with ValueError, leaving the
sender unchanged, when address isn't a valid email address.

#### `email.setHTML(any html) boolean`

**Deprecated.** Deprecated: use message.setBody(html, {html: true}) instead.

setHTML sets an HTML body and returns true.

#### `email.setSubject(string subject)`

setSubject sets the subject. Line breaks become spaces, so a subject
can't add headers.

#### `email.setText(any text)`

**Deprecated.** Deprecated: use message.setBody(text, {html: false}) instead.

setText sets a plain-text body.

#### `email.setTo(array addresses) result<unit, error>`

setTo replaces the To addresses with addresses, an array of strings;
addresses already on the message are skipped. Fails with ValueError,
changing nothing, when any address is invalid.

#### `email.stringify() string`

stringify returns the message exactly as send would transmit it: MIME
headers, a quoted-printable body and base64 attachments. Bcc addresses
are not included.

#### `email.subject() string`

subject returns the subject.

#### `email.to() string[]`

to returns the To addresses.

#### `email.toMap() {bcc: string[], body: string, cc: string[], from: string, html: boolean, subject: string, to: string[]}`

toMap returns the message as an object with the keys from, to, cc, bcc,
subject, body and html. Attachments are not included. email.new accepts
the same keys.

#### `email.validate() result<unit, error>`

validate checks that the message can be sent: it has a sender, at least
one To address and a subject. Fails with StateError naming what is
missing.

#### `email.wrapText(any width) boolean`

**Deprecated.** Deprecated: use message.setBody(text, {wrap: width}) instead.

wrapText wraps the body at width characters (78 when width is 0) and
returns true.
