Skip to content

Commit 6e41002

Browse files
address docs changes
1 parent 44f82e8 commit 6e41002

9 files changed

Lines changed: 267 additions & 80 deletions

File tree

‎src/docs/src/Email.md‎

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
---
22
title: Email
3-
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.
44
platforms: [websites, apps, nodejs, workers]
55
---
66

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

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

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

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+
<div class="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>
1414

15-
```js
16-
const user = await puter.auth.getUser();
17-
const granted = await puter.perms.request('permission', {
18-
permission: `fs:/${user.username}/.mail:read`,
19-
});
20-
```
15+
## Features
2116

22-
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.
17+
<div style="overflow:hidden; margin-bottom: 30px;">
18+
<div class="example-group active" data-section="list"><span>List Messages</span></div>
19+
<div class="example-group" data-section="get"><span>Read a Message</span></div>
20+
<div class="example-group" data-section="sendTransactional"><span>Send Transactional Email</span></div>
21+
</div>
22+
23+
<div class="example-content" data-section="list" style="display:block;">
24+
25+
#### List the ten newest messages in the inbox
2326

2427
```html;email-list
2528
<html>
@@ -37,15 +40,36 @@ Storage is the source of truth: a message is a file, and its `path` comes back o
3740
</html>
3841
```
3942

40-
## Sending transactional email
43+
</div>
4144

42-
<div class="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>
45+
<div class="example-content" data-section="get">
46+
47+
#### Read the newest message
48+
49+
```html;email-get
50+
<html>
51+
<body>
52+
<script src="https://js.puter.com/v2/"></script>
53+
<script>
54+
(async () => {
55+
const { items } = await puter.email.list({ limit: 1 });
56+
if (items.length === 0) return puter.print('Inbox is empty');
57+
58+
const message = await puter.email.get(items[0].id);
59+
puter.print(`From: ${message.from?.address}<br>`);
60+
puter.print(`Subject: ${message.subject}<br>`);
61+
puter.print(`<pre>${message.text}</pre>`);
62+
})();
63+
</script>
64+
</body>
65+
</html>
66+
```
4367

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

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.
70+
<div class="example-content" data-section="sendTransactional">
4771

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

5074
```js
5175
// In a worker: the worker authorizes the send, the calling user pays for it.
@@ -60,16 +84,17 @@ router.post('/notify', async ({ request, user }) => {
6084
});
6185
```
6286

63-
### Recipient protections
64-
65-
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>
7088

7189
## Functions
7290

7391
- **[`puter.email.list()`](/Email/list/)** - List the messages in the user's mailbox, newest first
7492
- **[`puter.email.get()`](/Email/get/)** - Fetch and parse one message, attachments included
7593
- **[`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:
98+
99+
- [List messages](/playground/email-list/)
100+
- [Read a message](/playground/email-get/)

‎src/docs/src/Email/get.md‎

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ platforms: [websites, apps, nodejs, workers]
66

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

9+
Reading the mailbox requires the `fs:/{username}/.mail:read` permission, the same grant [`puter.email.list()`](/Email/list/) needs.
10+
911
## Syntax
1012

1113
```js
@@ -27,41 +29,9 @@ The message id from `puter.email.list()`. Looks up the inbox.
2729

2830
## Return value
2931

30-
A `Promise` that resolves to the parsed message:
31-
32-
```js
33-
{
34-
id: '019...',
35-
folder: 'inbox',
36-
path: '/alice/.mail/objects/2026-03-15/019...--WW91ciBvcmRlcg',
37-
uid: 'a1b2c3...',
38-
size: 48213,
39-
date: '2026-03-15T12:34:56.000Z', // the Date header, or the filing time if unparseable
40-
subject: 'Your order has shipped',
41-
messageId: '<abc@example.com>',
42-
inReplyTo: null,
43-
references: null,
44-
from: { name: 'Example Shop', address: 'orders@example.com' },
45-
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.
6333

64-
With `raw: true`, the `Promise` resolves to a `Blob` of the message exactly as it was received.
34+
With `raw: true`, the `Promise` resolves to a `Blob` of the `message/rfc822` bytes exactly as they were received.
6535

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

‎src/docs/src/Email/list.md‎

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ platforms: [websites, apps, nodejs, workers]
66

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

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+
911
## Syntax
1012

1113
```js
@@ -26,29 +28,9 @@ puter.email.list(options)
2628

2729
## Return value
2830

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
38-
size: 48213, // raw message size in bytes
39-
folder: 'inbox',
40-
path: '/alice/.mail/objects/2026-03-15/019...--WW91ciBvcmRlcg',
41-
uid: 'a1b2c3...'
42-
},
43-
// ...
44-
],
45-
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: [] }`.
5032

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

5335
## Errors
5436

‎src/docs/src/Objects.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ Various object types and classes that represent different entities in the Puter
1212
- **[ChatResponse](/Objects/chatresponse/)** - Represents an AI chat response
1313
- **[ChatResponseChunk](/Objects/chatresponsechunk/)** - Represents a chunk of streaming chat response data
1414
- **[DetailedAppUsage](/Objects/detailedappusage/)** - Represents detailed resource usage statistics for a specific application
15+
- **[EmailListPage](/Objects/emaillistpage/)** - Represents one page of mailbox listing results
16+
- **[EmailMessage](/Objects/emailmessage/)** - Represents a fully parsed message from the user's mailbox
17+
- **[EmailMessageAttachment](/Objects/emailmessageattachment/)** - Represents one attachment of a parsed message, bytes included
18+
- **[EmailSummary](/Objects/emailsummary/)** - Represents one message in a mailbox listing
1519
- **[FSItem](/Objects/fsitem/)** - Represents a file or directory
1620
- **[KVPair](/Objects/kvpair/)** - Represents a key-value pair
1721
- **[MonthlyUsage](/Objects/monthlyusage/)** - Represents user's monthly resource usage information
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
title: EmailListPage
3+
description: One page of mailbox listing results from puter.email.list().
4+
---
5+
6+
An `EmailListPage` object is one page of results from [`puter.email.list()`](/Email/list/).
7+
8+
## Attributes
9+
10+
#### `items` (Array)
11+
12+
An array of [`EmailSummary`](/Objects/emailsummary/) objects, newest first.
13+
14+
#### `cursor` (String) (optional)
15+
16+
A pagination cursor for the next page. Present only while more pages exist. Pass it as `cursor` to the next `puter.email.list()` call to continue.
17+
18+
A page may hold fewer than `limit` items while `cursor` is still present — always iterate until `cursor` is absent rather than checking the page size.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
title: EmailMessage
3+
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.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
title: EmailMessageAttachment
3+
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:
37+
38+
```js
39+
new Blob([attachment.content], { type: attachment.mimeType })
40+
```
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
title: EmailSummary
3+
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)`.
33+
34+
#### `uid` (String)
35+
36+
The file system uid of the raw object.

0 commit comments

Comments
 (0)