You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
description: Read the user's Puter mailbox, and send transactional email from your app — receipts, alerts, confirmations — through a worker, from a Puter-controlled address.
3
+
description: Read the user's Puter mailbox and send transactional email from your app with Puter.js.
4
4
platforms: [websites, apps, nodejs, workers]
5
5
---
6
6
7
-
The Puter.js Email API has two halves. Your app can **read the user's mailbox**: every Puter account has an address, `{username}@puter.email`, and the mail it receives is stored in the user's own cloud drive as standard `message/rfc822` objects. And your app can **send transactional email**: mail your app sends to a person because of something they did — a signup confirmation, a receipt, a password reset, an alert.
7
+
The Email API lets your app read the user's Puter mailbox and send transactional email.
8
8
9
-
## Reading the user's mailbox
9
+
Every Puter account has an email address, `{username}@puter.email`. Mail sent to it is stored in the user's own cloud drive, so your app can list the inbox and read messages, attachments included, once the user grants access to their mailbox. Your app can also send transactional email — a signup confirmation, a receipt, an alert — from a Puter-controlled address, with no mail server, sending domain, or DKIM to set up.
10
10
11
-
Mail sent to `{username}@puter.email` lands under the user's `~/.mail` folder, one object per message, and the user's own outgoing mail is kept there too. `list()` pages through a folder newest first without downloading a single message, and `get()` fetches and parses one message, attachments included.
11
+
With the [User-Pays Model](/user-pays-model/), the account making the call covers its own usage: mail lives in the user's storage, and sends land on the caller's allowance rather than yours.
12
12
13
-
The mailbox belongs to the user, so it is protected like the rest of their files. Your app reads it only after the user grants `fs:/{username}/.mail:read`:
13
+
<divclass="info">Transactional email is available to accounts on a paid plan for now. A call from a free account fails with <code>402</code> <code>subscription_required</code>.</div>
Storage is the source of truth: a message is a file, and its `path` comes back on every listing item, so `puter.fs.read()` and `puter.fs.delete()` work on it directly.
@@ -37,15 +40,36 @@ Storage is the source of truth: a message is a file, and its `path` comes back o
37
40
</html>
38
41
```
39
42
40
-
## Sending transactional email
43
+
</div>
41
44
42
-
<divclass="info">Transactional email is available to accounts on a paid plan for now. A call from a free account fails with <code>402</code> <code>subscription_required</code>.</div>
Transactional email is not a mailing-list tool: a message reaches at most ten recipients, and every recipient can opt out of your app's mail with one click.
68
+
</div>
45
69
46
-
Mail goes out from a Puter-controlled address labelled with your app's title, for example`"My App" <my-app-no-reply@apps.puter.email>`, so you never set up a mail server, a sending domain, or DKIM. Replies go to your account email unless you say otherwise.
Every send is authorized by a **worker**. Either the worker sends directly, or your app sends with the user's session and passes the worker's token as `emailAccessToken`. In both cases the account that makes the call is the one billed and rate-limited — the [User-Pays Model](/user-pays-model/) — so your users' sends land on their own allowances rather than yours.
72
+
#### Send a transactional email from a worker
49
73
50
74
```js
51
75
// In a worker: the worker authorizes the send, the calling user pays for it.
Every message carries an unsubscribe link and a report-abuse link. Opting out is per app: a recipient who unsubscribes from your app stops getting your app's mail and still hears from other apps. Opted-out recipients are dropped from your later sends and come back in the result's `suppressed` array, so your app can stop asking. A send whose every `to` recipient has opted out is rejected.
66
-
67
-
### Mail the user sends as themselves
68
-
69
-
`puter.email.send()` is a separate surface: it sends a message from the user's own `<username>@puter.email` address, composed in the client and filed in the user's `~/.mail` folder, where `list({ folder: 'sent' })` finds it. Use `sendTransactional()` when your app is the sender; use `send()` when the user is.
87
+
</div>
70
88
71
89
## Functions
72
90
73
91
-**[`puter.email.list()`](/Email/list/)** - List the messages in the user's mailbox, newest first
74
92
-**[`puter.email.get()`](/Email/get/)** - Fetch and parse one message, attachments included
75
93
-**[`puter.email.sendTransactional()`](/Email/sendTransactional/)** - Send a transactional email from your app
94
+
95
+
## Examples
96
+
97
+
You can see the Puter.js Email features in action from the following examples:
Reads one message by the `id` a listing returned. The whole raw message is downloaded and parsed, so the result carries the full subject, sender and recipients, the text and HTML bodies, every header, and each attachment with its bytes. Pass `raw: true` to get the unparsed `message/rfc822` bytes as a `Blob` instead.
8
8
9
+
Reading the mailbox requires the `fs:/{username}/.mail:read` permission, the same grant [`puter.email.list()`](/Email/list/) needs.
10
+
9
11
## Syntax
10
12
11
13
```js
@@ -27,41 +29,9 @@ The message id from `puter.email.list()`. Looks up the inbox.
to: [{ name:'Alice', address:'alice@puter.email' }],
46
-
cc: [],
47
-
bcc: [], // populated only on sent copies
48
-
replyTo: [],
49
-
headers: [{ key:'subject', originalKey:'Subject', value:'Your order has shipped' }, /* ... */],
50
-
text:'Hi Alice, ...',
51
-
html:'<p>Hi Alice, ...</p>', // null when the message has no HTML part
52
-
attachments: [
53
-
{
54
-
filename:'invoice.pdf',
55
-
mimeType:'application/pdf',
56
-
disposition:'attachment',
57
-
size:30211,
58
-
content:ArrayBuffer// the decoded bytes
59
-
}
60
-
]
61
-
}
62
-
```
32
+
A `Promise` that resolves to an [`EmailMessage`](/Objects/emailmessage/) object: the full subject, sender and recipients, the text and HTML bodies, every header, and the attachments as [`EmailMessageAttachment`](/Objects/emailmessageattachment/) objects with their decoded bytes.
63
33
64
-
With `raw: true`, the `Promise` resolves to a `Blob` of the messageexactly as it was received.
34
+
With `raw: true`, the `Promise` resolves to a `Blob` of the `message/rfc822` bytes exactly as they were received.
65
35
66
36
Messages can be up to 25 MiB, and `get()` holds the whole message in memory while parsing. For a message list, use `puter.email.list()` and read messages one at a time as the user opens them.
Lists the messages in one folder of the user's mailbox, newest first. Every call returns one page of `items` plus, while more pages exist, a `cursor` to pass back for the next one. Listing reads the mailbox's folder structure only, so it never downloads a message: each item carries the subject, date, and size, and `puter.email.get()` fetches the rest.
8
8
9
+
The mailbox belongs to the user, so it is protected like the rest of their files. An app reads it only after the user grants `fs:/{username}/.mail:read`, for example with `puter.perms.request('permission', { permission: `fs:/${username}/.mail:read` })`.
10
+
9
11
## Syntax
10
12
11
13
```js
@@ -26,29 +28,9 @@ puter.email.list(options)
26
28
27
29
## Return value
28
30
29
-
A `Promise` that resolves to a page:
30
-
31
-
```js
32
-
{
33
-
items: [
34
-
{
35
-
id:'019...', // pass to puter.email.get()
36
-
subject:'Your order has shipped', // may be truncated to 80 characters
37
-
date:'2026-03-15T12:34:56.789Z', // when the message was filed
cursor:'eyJ2IjoxLCJmIjoiaW5ib3giLCJkIjoiMjAyNi0wMy0xNSJ9'// only while more pages exist
46
-
}
47
-
```
48
-
49
-
A page may hold fewer than `limit` items while more pages exist. Iterate until `cursor` is absent rather than checking the page size. A mailbox that has never received mail lists as `{ items: [] }`.
31
+
A `Promise` that resolves to an [`EmailListPage`](/Objects/emaillistpage/) object: its `items` are [`EmailSummary`](/Objects/emailsummary/) objects, newest first, and its `cursor` is present only while more pages exist. A page may hold fewer than `limit` items while more pages exist, so iterate until `cursor` is absent rather than checking the page size. A mailbox that has never received mail lists as `{ items: [] }`.
50
32
51
-
With `stream: true`, the method returns an `AsyncIterableIterator` of such pages.
33
+
With `stream: true`, the method returns an `AsyncIterableIterator` of [`EmailListPage`](/Objects/emaillistpage/) objects instead.
description: A fully parsed message from the user's mailbox, returned by puter.email.get().
4
+
---
5
+
6
+
An `EmailMessage` object is a message from the user's mailbox, downloaded and parsed by [`puter.email.get()`](/Email/get/). It has every attribute of an [`EmailSummary`](/Objects/emailsummary/) plus the parsed contents below.
7
+
8
+
## Attributes
9
+
10
+
#### `id` (String)
11
+
12
+
The message id.
13
+
14
+
#### `folder` (String)
15
+
16
+
The folder the message was read from: `'inbox'` or `'sent'`.
17
+
18
+
#### `path` (String)
19
+
20
+
The path of the raw `message/rfc822` object in the user's cloud drive.
21
+
22
+
#### `uid` (String)
23
+
24
+
The file system uid of the raw object.
25
+
26
+
#### `size` (Number)
27
+
28
+
The size of the raw message in bytes.
29
+
30
+
#### `date` (String)
31
+
32
+
An ISO 8601 timestamp taken from the message's `Date` header, or the filing time when the header is missing or unparseable.
33
+
34
+
#### `subject` (String)
35
+
36
+
The full subject line.
37
+
38
+
#### `messageId` (String | null)
39
+
40
+
The `Message-ID` header, angle brackets included, or `null`.
41
+
42
+
#### `inReplyTo` (String | null)
43
+
44
+
The `In-Reply-To` header, or `null`.
45
+
46
+
#### `references` (String | null)
47
+
48
+
The `References` header, or `null`.
49
+
50
+
#### `from` (Object | null)
51
+
52
+
The sender, as `{ name, address }`, or `null` when the message has no `From` header.
53
+
54
+
#### `to` (Array)
55
+
56
+
The `To` recipients, each `{ name, address }`. An address group appears as `{ name, group: [...] }`.
57
+
58
+
#### `cc` (Array)
59
+
60
+
The `Cc` recipients, in the same shape as `to`.
61
+
62
+
#### `bcc` (Array)
63
+
64
+
The `Bcc` recipients, in the same shape as `to`. Only a message in the `sent` folder carries them.
65
+
66
+
#### `replyTo` (Array)
67
+
68
+
The `Reply-To` addresses, in the same shape as `to`.
69
+
70
+
#### `headers` (Array)
71
+
72
+
Every header of the message, each `{ key, originalKey, value }` where `key` is the lowercase name and `originalKey` preserves the original case.
73
+
74
+
#### `text` (String | null)
75
+
76
+
The plain-text body, or `null` when the message has none.
77
+
78
+
#### `html` (String | null)
79
+
80
+
The HTML body, or `null` when the message has none.
81
+
82
+
#### `attachments` (Array)
83
+
84
+
The message's attachments, each an [`EmailMessageAttachment`](/Objects/emailmessageattachment/) with its decoded bytes.
description: One attachment of a message returned by puter.email.get(), bytes included.
4
+
---
5
+
6
+
An `EmailMessageAttachment` object is one attachment of an [`EmailMessage`](/Objects/emailmessage/). The whole message is downloaded when it is read, so every attachment comes with its decoded bytes.
7
+
8
+
## Attributes
9
+
10
+
#### `filename` (String | null)
11
+
12
+
The attachment's file name, or `null` when the message did not give one.
13
+
14
+
#### `mimeType` (String)
15
+
16
+
The attachment's MIME type, for example `application/pdf`.
17
+
18
+
#### `disposition` (String | null)
19
+
20
+
`'attachment'`, `'inline'`, or `null` when the message did not say.
21
+
22
+
#### `contentId` (String) (optional)
23
+
24
+
The `Content-ID`, without angle brackets. Inline images are referenced from the HTML body as `cid:` URLs with this value.
25
+
26
+
#### `related` (Boolean) (optional)
27
+
28
+
`true` for parts referenced from the HTML body, such as inline images.
29
+
30
+
#### `size` (Number)
31
+
32
+
The byte length of `content`.
33
+
34
+
#### `content` (ArrayBuffer)
35
+
36
+
The decoded attachment bytes. Wrap them in a `Blob` to save or display them:
description: One message as it appears in a mailbox listing from puter.email.list().
4
+
---
5
+
6
+
An `EmailSummary` object represents one message in a page returned by [`puter.email.list()`](/Email/list/). It is built from the mailbox listing alone, without opening the message, so it carries what the listing knows: the id, a subject, the filing time, and the size. Pass its `id` to [`puter.email.get()`](/Email/get/) for the full [`EmailMessage`](/Objects/emailmessage/).
7
+
8
+
## Attributes
9
+
10
+
#### `id` (String)
11
+
12
+
The message id. Unique within a folder; pass it to `puter.email.get()`.
13
+
14
+
#### `subject` (String)
15
+
16
+
The subject line, possibly truncated to 80 characters. The full subject is on the [`EmailMessage`](/Objects/emailmessage/).
17
+
18
+
#### `date` (String)
19
+
20
+
An ISO 8601 timestamp of when the message was filed in the mailbox.
21
+
22
+
#### `size` (Number)
23
+
24
+
The size of the raw message in bytes.
25
+
26
+
#### `folder` (String)
27
+
28
+
The folder the message was listed from: `'inbox'` or `'sent'`.
29
+
30
+
#### `path` (String)
31
+
32
+
The path of the raw `message/rfc822` object in the user's cloud drive. It works with the file system API directly, for example `puter.fs.read(path)` or `puter.fs.delete(path)`.
0 commit comments