JMAP transport for the Upyo email library. Implements RFC 8620 (core) and RFC 8621 (mail) for sending emails via JMAP protocol.
- RFC 8620 (JMAP Core) and RFC 8621 (JMAP Mail) compliant
- Bearer token authentication
- Automatic session discovery and caching
- Automatic identity resolution from sender email
- File attachment support (blob upload)
- Inline attachment support (multipart/related)
- Exponential backoff retry with configurable attempts
- Request timeout support
- AbortSignal support for cancellation
npm add @upyo/core @upyo/jmap
pnpm add @upyo/core @upyo/jmap
yarn add @upyo/core @upyo/jmap
deno add --jsr @upyo/core @upyo/jmap
bun add @upyo/core @upyo/jmapimport { createMessage } from "@upyo/core";
import { JmapTransport } from "@upyo/jmap";
import process from "node:process";
const message = createMessage({
from: "sender@example.com",
to: "recipient@example.net",
subject: "Hello from Upyo!",
content: { text: "This is a test email." },
});
const transport = new JmapTransport({
sessionUrl: process.env.JMAP_SESSION_URL!,
bearerToken: process.env.JMAP_BEARER_TOKEN!,
});
const receipt = await transport.send(message);
if (receipt.successful) {
console.log("Message sent with ID:", receipt.messageId);
} else {
console.error("Send failed:", receipt.errorMessages.join(", "));
}Either bearerToken or basicAuth must be provided:
| Option | Type | Description |
|---|---|---|
sessionUrl |
string |
JMAP session URL (e.g., https://server/.well-known/jmap) |
bearerToken |
string |
Bearer token for authentication |
basicAuth |
{ username: string, password: string } |
Basic authentication credentials |
| Option | Type | Default | Description |
|---|---|---|---|
accountId |
string |
Auto-detected | JMAP account ID (defaults to first account with mail capability) |
identityId |
string |
Auto-resolved | Identity ID for sending (defaults to identity matching sender email) |
timeout |
number |
30000 |
Request timeout in milliseconds |
retries |
number |
3 |
Number of retry attempts for failed requests |
sessionCacheTtl |
number |
300000 |
Session cache TTL in milliseconds (5 minutes) |
headers |
Record<string, string> |
{} |
Additional HTTP headers to include in requests |
baseUrl |
string |
- | Base URL for rewriting session URLs (useful when server returns internal hostnames) |
The transport returns a Receipt discriminated union:
const receipt = await transport.send(message);
if (receipt.successful) {
// Success: receipt.messageId contains the submission ID
console.log("Sent:", receipt.messageId);
} else {
// Failure: receipt.errorMessages contains error details
console.error("Failed:", receipt.errorMessages);
}You can also catch specific JMAP errors:
import { JmapApiError, isCapabilityError } from "@upyo/jmap";
try {
const receipt = await transport.send(message);
} catch (error) {
if (error instanceof JmapApiError) {
console.error("JMAP error:", error.statusCode, error.responseBody);
}
if (isCapabilityError(error)) {
console.error("Server missing required capabilities");
}
}