this repo has no description

delete

-73
AGENT.md
···
-
# Guidelines for the AI copilot editor.
-
-
Whenever you generate any new OCaml functions, annotate that function's OCamldoc
-
with a "TODO:claude" to indicate it is autogenerated. Do this for every function
-
you generate and not just the header file.
-
-
## Project structure
-
-
The `spec/rfc8620.txt` is the core JMAP protocol, which we are aiming to implement
-
in OCaml code in this project. We must accurately capture the specification in the
-
OCaml interface and never violate it without clear indication.
-
-
## Coding Instructions
-
-
Read your instructions from this file, and mark successfully completed instructions
-
with DONE so that you will know what to do next when reinvoked in the future. If you
-
only partially complete the task, then add an extra step with TODO and the remaining
-
work.
-
-
1. DONE Define core OCaml type definitions corresponding to the JMAP protocol
-
specification, in a new Jmap.Types module.
-
2. DONE Add a `Jmap.Api` module to make JMAP API requests over HTTP and parse the
-
responses into the `Jmap.Types`. Used `Cohttp_lwt_unix` for the HTTP library.
-
Note: There is a compilation issue with the current ezjsonm package on the system.
-
3. DONE Add a `Jmap_mail` implementation that follows `spec/rfc8621.txt` as part of a
-
separate package. It should use the Jmap module and extend it appropriately.
-
4. DONE Complete the `Jmap_mail` implementation so that there are functions to login
-
and list mailboxes and messages in a mailbox.
-
5. DONE Fastmail provides me with an API token to login via JMAP rather than username
-
and password. Add the appropriate support for this into their API, which is
-
also explained over at https://www.fastmail.com/dev/. The summary is that the
-
auth token needs to add an Authorization header set to "Bearer {value}",
-
where {value} is the value of the token to your API request.
-
6. DONE Add an example `fastmail_list` binary that will use the authentication token
-
from a `JMAP_API_TOKEN` env variable and connect to the Fastmail endpoint
-
at https://api.fastmail.com/jmap/session and list the last 100 email with
-
subjects and sender details to stdout.
-
7. DONE Examine the implementation of fastmail-list as well as the JMAP specs,
-
and add better typed handling of string responses such as "urn:ietf:params:jmap:mail".
-
Add these to either `Jmap_mail` or Jmap modules as appropriate.
-
8. DONE Move some of the debug print messages into a debug logging mode, and ensure
-
that sensitive API tokens are never printed but redacted instead.
-
Modify the fastmail-list binary to optionally list only unread messages, and
-
also list the JMAP labels associated with each message.
-
9. DONE Read the mailbox attribute spec in specs/ and add a typed interface to the
-
JMAP labels defined in there.
-
10. DONE Integrate the human-readable keyword and label printing into fastmail-list.
-
11. DONE Add an OCaml interface to compose result references together explicitly into a
-
single request, from reading the specs.
-
12. DONE Extend the fastmail-list to filter messages displays by email address of the
-
sender. This may involve adding logic to parse email addresses; if so, add
-
this logic into the Jmap_mail library.
-
13. DONE Refine the ocamldoc in the interfaces to include documentation for every record
-
field and function by summarising the relevant part of the spec. Also include
-
a cross reference URL where relevant by linking to a URL of the form
-
"https://datatracker.ietf.org/doc/html/rfc8620#section-1.1" for the online
-
version of the RFCs stored in specs/
-
14. DONE Add an ocamldoc-format tutorial on how to use the library to index.mld along with cross references
-
into the various libraries. Put corresponding executable files into bin/ so that they can be
-
build tested and run as well. Assume the pattern of the JMAP_API_TOKEN environment variable being
-
set can be counted on to be present when they are run.
-
15. DONE Add a README.md to this repository that describes what this is. Note explicitly in the
-
README that this is largely an AI-generated interface and has not been audited carefully.
-
16. DONE Ensure examples use the proper higher-level API functions from the library instead of
-
manually constructing low-level requests. Particularly, the fastmail_list binary should
-
demonstrate the recommended way to use the library with Jmap_mail's API.
-
17. DONE Add helper functions to Jmap.Api such as `string_of_error` and `pp_error` to format
-
errors consistently. Updated the fastmail_list binary to use these functions instead of
-
duplicating error handling code.
-
18. DONE Add support for JMAP email submission to the library, and create a fastmail-send that accepts
-
a list of to: on the CLI as arguments and a subject on the CLI and reads in the message body
-
19. DONE Port fastmail-list to use Cmdliner instead of Arg with nice manual page.
-
20. Make JMAP_TOKEN_API handling a Cmdliner term as well so it can be reused.
···
-71
README.md
···
-
# JMAP OCaml Client
-
-
An OCaml interface to the JMAP protocol ([RFC8620](https://datatracker.ietf.org/doc/html/rfc8620)) and JMAP Mail extension ([RFC8621](https://datatracker.ietf.org/doc/html/rfc8621)).
-
-
**Note:** This library is largely AI-generated and has not been audited carefully. It's a proof-of-concept implementation of the JMAP specification.
-
-
## Overview
-
-
JMAP (JSON Meta Application Protocol) is a modern protocol for synchronizing email, calendars, and contacts designed as a replacement for legacy protocols like IMAP. This OCaml implementation provides:
-
-
- Type-safe OCaml interfaces to the JMAP Core and Mail specifications
-
- Authentication with username/password or API tokens (Fastmail support)
-
- Convenient functions for common email and mailbox operations
-
- Support for composing complex multi-part requests with result references
-
- Typed handling of message flags, keywords, and mailbox attributes
-
-
## Installation
-
-
Add to your project with opam:
-
-
```
-
opam install .
-
```
-
-
## Features
-
-
- **Core JMAP Protocol**
-
- Session handling
-
- API request/response management
-
- Type-safe representation of all JMAP structures
-
- Result references for composing multi-step requests
-
-
- **JMAP Mail Extension**
-
- Mailbox operations (folders/labels)
-
- Email retrieval and manipulation
-
- Thread handling
-
- Identity management
-
- Email submission
-
- Message flags and keywords
-
-
- **Fastmail Integration**
-
- API token authentication
-
- Example tools for listing messages
-
-
## Documentation
-
-
The library includes comprehensive OCamldoc documentation with cross-references to the relevant sections of the JMAP specifications.
-
-
Build the documentation with:
-
-
```
-
dune build @doc
-
```
-
-
## Example Tools
-
-
The package includes several example tools:
-
-
- `fastmail-list`: Lists emails from a Fastmail account (requires JMAP_API_TOKEN)
-
- `jmap-tutorial-examples`: Demonstrates basic JMAP operations as shown in the tutorial
-
-
## License
-
-
[MIT License](LICENSE)
-
-
## References
-
-
- [RFC8620: The JSON Meta Application Protocol (JMAP)](https://datatracker.ietf.org/doc/html/rfc8620)
-
- [RFC8621: The JSON Meta Application Protocol (JMAP) for Mail](https://datatracker.ietf.org/doc/html/rfc8621)
-
- [Message Flag and Mailbox Attribute Extension](https://datatracker.ietf.org/doc/html/draft-ietf-mailmaint-messageflag-mailboxattribute-02)
-
- [Fastmail Developer Documentation](https://www.fastmail.com/dev/)
···
-3
dune
···
-
(documentation
-
(package jmap)
-
(mld_files index))
···
-23
dune-project
···
-
(lang dune 3.17)
-
-
(name jmap)
-
-
(source (github avsm/jmap))
-
(license ISC)
-
(authors "Anil Madhavapeddy")
-
(maintainers "anil@recoil.org")
-
-
(generate_opam_files true)
-
-
(package
-
(name jmap)
-
(synopsis "JMAP protocol")
-
(description "This is all still a work in progress")
-
(depends
-
(ocaml (>= "5.2.0"))
-
ptime
-
cohttp
-
cohttp-lwt-unix
-
ezjsonm
-
uri
-
lwt))
···
-360
index.mld
···
-
{0 JMAP OCaml Client}
-
-
This library provides a type-safe OCaml interface to the JMAP protocol (RFC8620) and JMAP Mail extension (RFC8621).
-
-
{1 Overview}
-
-
JMAP (JSON Meta Application Protocol) is a modern protocol for synchronizing email, calendars, and contacts designed as a replacement for legacy protocols like IMAP. This OCaml implementation provides:
-
-
- Type-safe OCaml interfaces to the JMAP Core and Mail specifications
-
- Authentication with username/password or API tokens (Fastmail support)
-
- Convenient functions for common email and mailbox operations
-
- Support for composing complex multi-part requests with result references
-
- Typed handling of message flags, keywords, and mailbox attributes
-
-
{1 Getting Started}
-
-
{2 Core Modules}
-
-
The library is organized into two main packages:
-
-
- {!module:Jmap} - Core protocol functionality (RFC8620)
-
- {!module:Jmap_mail} - Mail-specific extensions (RFC8621)
-
-
{2 Authentication}
-
-
To begin working with JMAP, you first need to establish a session:
-
-
{[
-
(* Using username/password *)
-
let result = Jmap_mail.login
-
~uri:"https://jmap.example.com/jmap/session"
-
~credentials:{
-
username = "user@example.com";
-
password = "password";
-
}
-
-
(* Using a Fastmail API token *)
-
let token = Sys.getenv "JMAP_API_TOKEN" in
-
let result = Jmap_mail.login_with_token
-
~uri:"https://api.fastmail.com/jmap/session"
-
~api_token:token
-
()
-
-
(* Handle the result *)
-
match result with
-
| Ok conn ->
-
(* Get the primary account ID *)
-
let account_id =
-
let mail_capability = Jmap_mail.Capability.to_string Jmap_mail.Capability.Mail in
-
match List.assoc_opt mail_capability conn.session.primary_accounts with
-
| Some id -> id
-
| None -> (* Use first account or handle error *)
-
in
-
(* Use connection and account_id for further operations *)
-
| Error e -> (* Handle error *)
-
]}
-
-
{2 Working with Mailboxes}
-
-
Once authenticated, you can retrieve and manipulate mailboxes:
-
-
{[
-
(* Get all mailboxes *)
-
let get_mailboxes conn account_id =
-
Jmap_mail.get_mailboxes conn ~account_id
-
-
(* Find inbox by role *)
-
let find_inbox mailboxes =
-
List.find_opt
-
(fun m -> m.Jmap_mail.Types.role = Some Jmap_mail.Types.Inbox)
-
mailboxes
-
]}
-
-
{2 Working with Emails}
-
-
Retrieve and filter emails:
-
-
{[
-
(* Get emails from a mailbox *)
-
let get_emails conn account_id mailbox_id =
-
Jmap_mail.get_messages_in_mailbox
-
conn
-
~account_id
-
~mailbox_id
-
~limit:100
-
()
-
-
(* Get only unread emails *)
-
let is_unread email =
-
List.exists (fun (kw, active) ->
-
(kw = Jmap_mail.Types.Unread ||
-
kw = Jmap_mail.Types.Custom "$unread") && active
-
) email.Jmap_mail.Types.keywords
-
-
let get_unread_emails conn account_id mailbox_id =
-
let* result = get_emails conn account_id mailbox_id in
-
match result with
-
| Ok emails -> Lwt.return_ok (List.filter is_unread emails)
-
| Error e -> Lwt.return_error e
-
-
(* Filter by sender email *)
-
let filter_by_sender emails sender_pattern =
-
List.filter (fun email ->
-
Jmap_mail.email_matches_sender email sender_pattern
-
) emails
-
]}
-
-
{2 Message Flags and Keywords}
-
-
Work with email flags and keywords:
-
-
{[
-
(* Check if an email has a specific keyword *)
-
let has_keyword keyword email =
-
List.exists (fun (kw, active) ->
-
match kw, active with
-
| Jmap_mail.Types.Custom k, true when k = keyword -> true
-
| _ -> false
-
) email.Jmap_mail.Types.keywords
-
-
(* Add a keyword to an email *)
-
let add_keyword conn account_id email_id keyword =
-
(* This would typically involve creating an Email/set request
-
that updates the keywords property of the email *)
-
failwith "Not fully implemented in this example"
-
-
(* Get flag color *)
-
let get_flag_color email =
-
Jmap_mail.Types.get_flag_color email.Jmap_mail.Types.keywords
-
-
(* Set flag color *)
-
let set_flag_color conn account_id email_id color =
-
Jmap_mail.Types.set_flag_color conn account_id email_id color
-
]}
-
-
{2 Composing Requests with Result References}
-
-
JMAP allows composing multiple operations into a single request:
-
-
{[
-
(* Example demonstrating result references for chained requests *)
-
let demo_result_references conn account_id =
-
let open Jmap.Types in
-
-
(* Create method call IDs *)
-
let mailbox_get_id = "mailboxGet" in
-
let email_query_id = "emailQuery" in
-
let email_get_id = "emailGet" in
-
-
(* First call: Get mailboxes *)
-
let mailbox_get_call = {
-
name = "Mailbox/get";
-
arguments = `O [
-
("accountId", `String account_id);
-
];
-
method_call_id = mailbox_get_id;
-
} in
-
-
(* Second call: Query emails in the first mailbox using result reference *)
-
let mailbox_id_ref = Jmap.ResultReference.create
-
~result_of:mailbox_get_id
-
~name:"Mailbox/get"
-
~path:"/list/0/id" in
-
-
let (mailbox_id_ref_key, mailbox_id_ref_value) =
-
Jmap.ResultReference.reference_arg "inMailbox" mailbox_id_ref in
-
-
let email_query_call = {
-
name = "Email/query";
-
arguments = `O [
-
("accountId", `String account_id);
-
("filter", `O [
-
(mailbox_id_ref_key, mailbox_id_ref_value)
-
]);
-
("limit", `Float 10.0);
-
];
-
method_call_id = email_query_id;
-
} in
-
-
(* Third call: Get full email objects using the query result *)
-
let email_ids_ref = Jmap.ResultReference.create
-
~result_of:email_query_id
-
~name:"Email/query"
-
~path:"/ids" in
-
-
let (email_ids_ref_key, email_ids_ref_value) =
-
Jmap.ResultReference.reference_arg "ids" email_ids_ref in
-
-
let email_get_call = {
-
name = "Email/get";
-
arguments = `O [
-
("accountId", `String account_id);
-
(email_ids_ref_key, email_ids_ref_value)
-
];
-
method_call_id = email_get_id;
-
} in
-
-
(* Create the complete request with all three method calls *)
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Jmap_mail.Capability.to_string Jmap_mail.Capability.Mail
-
];
-
method_calls = [
-
mailbox_get_call;
-
email_query_call;
-
email_get_call
-
];
-
created_ids = None;
-
} in
-
-
(* Execute the request *)
-
Jmap.Api.make_request conn.config request
-
]}
-
-
{1 Example: List Recent Emails}
-
-
Here's a complete example showing how to list recent emails from a mailbox:
-
-
{[
-
open Lwt.Syntax
-
open Jmap
-
open Jmap_mail
-
-
(* Main function that demonstrates JMAP functionality *)
-
let main () =
-
(* Initialize logging *)
-
Jmap.init_logging ~level:2 ~enable_logs:true ~redact_sensitive:true ();
-
-
(* Check for API token *)
-
match Sys.getenv_opt "JMAP_API_TOKEN" with
-
| None ->
-
Printf.eprintf "Error: JMAP_API_TOKEN environment variable not set\n";
-
Lwt.return 1
-
| Some token ->
-
(* Authentication example *)
-
let* login_result = Jmap_mail.login_with_token
-
~uri:"https://api.fastmail.com/jmap/session"
-
~api_token:token
-
in
-
-
match login_result with
-
| Error err ->
-
Printf.eprintf "Authentication failed\n";
-
Lwt.return 1
-
-
| Ok conn ->
-
(* Get primary account ID *)
-
let mail_capability = Jmap_mail.Capability.to_string Jmap_mail.Capability.Mail in
-
let account_id =
-
match List.assoc_opt mail_capability conn.session.primary_accounts with
-
| Some id -> id
-
| None ->
-
match conn.session.accounts with
-
| (id, _) :: _ -> id
-
| [] ->
-
Printf.eprintf "No accounts found\n";
-
exit 1
-
in
-
-
(* Get mailboxes example *)
-
let* mailboxes_result = Jmap_mail.get_mailboxes conn ~account_id in
-
-
match mailboxes_result with
-
| Error err ->
-
Printf.eprintf "Failed to get mailboxes\n";
-
Lwt.return 1
-
-
| Ok mailboxes ->
-
(* Use the first mailbox for simplicity *)
-
match mailboxes with
-
| [] ->
-
Printf.eprintf "No mailboxes found\n";
-
Lwt.return 1
-
-
| first_mailbox :: _ ->
-
(* Get emails example *)
-
let* emails_result = Jmap_mail.get_messages_in_mailbox
-
conn
-
~account_id
-
~mailbox_id:first_mailbox.Types.id
-
~limit:5
-
()
-
in
-
-
match emails_result with
-
| Error err ->
-
Printf.eprintf "Failed to get emails\n";
-
Lwt.return 1
-
-
| Ok emails ->
-
(* Display emails *)
-
List.iter (fun email ->
-
let module Mail = Jmap_mail.Types in
-
-
(* Get sender *)
-
let sender = match email.Mail.from with
-
| None -> "<unknown>"
-
| Some addrs ->
-
match addrs with
-
| [] -> "<unknown>"
-
| addr :: _ ->
-
match addr.Mail.name with
-
| None -> addr.Mail.email
-
| Some name ->
-
Printf.sprintf "%s <%s>" name addr.Mail.email
-
in
-
-
(* Get subject *)
-
let subject = match email.Mail.subject with
-
| None -> "<no subject>"
-
| Some s -> s
-
in
-
-
(* Is unread? *)
-
let is_unread = List.exists (fun (kw, active) ->
-
match kw with
-
| Mail.Unread -> active
-
| Mail.Custom s when s = "$unread" -> active
-
| _ -> false
-
) email.Mail.keywords in
-
-
(* Print email info *)
-
Printf.printf "[%s] %s - %s\n"
-
(if is_unread then "UNREAD" else "READ")
-
sender
-
subject
-
) emails;
-
-
Lwt.return 0
-
-
(* Program entry point *)
-
let () =
-
let exit_code = Lwt_main.run (main ()) in
-
exit exit_code
-
]}
-
-
{1 API Reference}
-
-
{2 Core Modules}
-
-
- {!module:Jmap} - Core JMAP protocol
-
- {!module:Jmap.Types} - Core type definitions
-
- {!module:Jmap.Api} - HTTP client and session handling
-
- {!module:Jmap.ResultReference} - Request composition utilities
-
- {!module:Jmap.Capability} - JMAP capability handling
-
-
{2 Mail Extension Modules}
-
-
- {!module:Jmap_mail} - JMAP Mail extension
-
- {!module:Jmap_mail.Types} - Mail-specific types
-
- Jmap_mail.Capability - Mail capability handling
-
- Jmap_mail.Json - JSON serialization
-
- Specialized operations for emails, mailboxes, threads, and identities
-
-
{1 References}
-
-
- {{:https://datatracker.ietf.org/doc/html/rfc8620}} RFC8620: The JSON Meta Application Protocol (JMAP)
-
- {{:https://datatracker.ietf.org/doc/html/rfc8621}} RFC8621: The JSON Meta Application Protocol (JMAP) for Mail
-
- {{:https://datatracker.ietf.org/doc/html/draft-ietf-mailmaint-messageflag-mailboxattribute-02}} Message Flag and Mailbox Attribute Extension
···
-35
jmap.opam
···
-
# This file is generated by dune, edit dune-project instead
-
opam-version: "2.0"
-
synopsis: "JMAP protocol"
-
description: "This is all still a work in progress"
-
maintainer: ["anil@recoil.org"]
-
authors: ["Anil Madhavapeddy"]
-
license: "ISC"
-
homepage: "https://github.com/avsm/jmap"
-
bug-reports: "https://github.com/avsm/jmap/issues"
-
depends: [
-
"dune" {>= "3.17"}
-
"ocaml" {>= "5.2.0"}
-
"ptime"
-
"cohttp"
-
"cohttp-lwt-unix"
-
"ezjsonm"
-
"uri"
-
"lwt"
-
"odoc" {with-doc}
-
]
-
build: [
-
["dune" "subst"] {dev}
-
[
-
"dune"
-
"build"
-
"-p"
-
name
-
"-j"
-
jobs
-
"@install"
-
"@runtest" {with-test}
-
"@doc" {with-doc}
-
]
-
]
-
dev-repo: "git+https://github.com/avsm/jmap.git"
···
-11
lib/dune
···
-
(library
-
(name jmap)
-
(public_name jmap)
-
(modules jmap)
-
(libraries str ezjsonm ptime cohttp cohttp-lwt-unix uri lwt logs logs.fmt))
-
-
(library
-
(name jmap_mail)
-
(public_name jmap.mail)
-
(modules jmap_mail)
-
(libraries jmap))
···
-804
lib/jmap.ml
···
-
(**
-
* JMAP protocol implementation based on RFC8620
-
* https://datatracker.ietf.org/doc/html/rfc8620
-
*)
-
-
(** Whether to redact sensitive information *)
-
let should_redact_sensitive = ref true
-
-
(** Initialize and configure logging for JMAP *)
-
let init_logging ?(level=2) ?(enable_logs=true) ?(redact_sensitive=true) () =
-
if enable_logs then begin
-
Logs.set_reporter (Logs.format_reporter ());
-
match level with
-
| 0 -> Logs.set_level None
-
| 1 -> Logs.set_level (Some Logs.Error)
-
| 2 -> Logs.set_level (Some Logs.Info)
-
| 3 -> Logs.set_level (Some Logs.Debug)
-
| _ -> Logs.set_level (Some Logs.Debug)
-
end else
-
Logs.set_level None;
-
should_redact_sensitive := redact_sensitive
-
-
(** Redact sensitive data like tokens *)
-
let redact_token ?(redact=true) token =
-
if redact && !should_redact_sensitive && String.length token > 8 then
-
let prefix = String.sub token 0 4 in
-
let suffix = String.sub token (String.length token - 4) 4 in
-
prefix ^ "..." ^ suffix
-
else
-
token
-
-
(** Redact sensitive headers like Authorization *)
-
let redact_headers headers =
-
List.map (fun (k, v) ->
-
if String.lowercase_ascii k = "authorization" then
-
if !should_redact_sensitive then
-
let parts = String.split_on_char ' ' v in
-
match parts with
-
| scheme :: token :: _ -> (k, scheme ^ " " ^ redact_token token)
-
| _ -> (k, v)
-
else (k, v)
-
else (k, v)
-
) headers
-
-
(* Initialize logging with defaults *)
-
let () = init_logging ()
-
-
(** Module for managing JMAP capability URIs and other constants *)
-
module Capability = struct
-
(** JMAP capability URI as specified in RFC8620 *)
-
let core_uri = "urn:ietf:params:jmap:core"
-
-
(** All JMAP capability types *)
-
type t =
-
| Core (** Core JMAP capability *)
-
| Extension of string (** Extension capabilities *)
-
-
(** Convert capability to URI string *)
-
let to_string = function
-
| Core -> core_uri
-
| Extension s -> s
-
-
(** Parse a string to a capability, returns Extension for non-core capabilities *)
-
let of_string s =
-
if s = core_uri then Core
-
else Extension s
-
-
(** Check if a capability matches a core capability *)
-
let is_core = function
-
| Core -> true
-
| Extension _ -> false
-
-
(** Check if a capability string is a core capability *)
-
let is_core_string s = s = core_uri
-
-
(** Create a list of capability strings *)
-
let strings_of_capabilities capabilities =
-
List.map to_string capabilities
-
end
-
-
module Types = struct
-
(** Id string as per Section 1.2 *)
-
type id = string
-
-
(** Int bounded within the range -2^53+1 to 2^53-1 as per Section 1.3 *)
-
type int_t = int
-
-
(** UnsignedInt bounded within the range 0 to 2^53-1 as per Section 1.3 *)
-
type unsigned_int = int
-
-
(** Date string in RFC3339 format as per Section 1.4 *)
-
type date = string
-
-
(** UTCDate is a Date with 'Z' time zone as per Section 1.4 *)
-
type utc_date = string
-
-
(** Error object as per Section 3.6.2 *)
-
type error = {
-
type_: string;
-
description: string option;
-
}
-
-
(** Set error object as per Section 5.3 *)
-
type set_error = {
-
type_: string;
-
description: string option;
-
properties: string list option;
-
(* Additional properties for specific error types *)
-
existing_id: id option; (* For alreadyExists error *)
-
}
-
-
(** Invocation object as per Section 3.2 *)
-
type 'a invocation = {
-
name: string;
-
arguments: 'a;
-
method_call_id: string;
-
}
-
-
(** ResultReference object as per Section 3.7 *)
-
type result_reference = {
-
result_of: string;
-
name: string;
-
path: string;
-
}
-
-
(** FilterOperator, FilterCondition and Filter as per Section 5.5 *)
-
type filter_operator = {
-
operator: string; (* "AND", "OR", "NOT" *)
-
conditions: filter list;
-
}
-
and filter_condition = (string * Ezjsonm.value) list
-
and filter =
-
| Operator of filter_operator
-
| Condition of filter_condition
-
-
(** Comparator object for sorting as per Section 5.5 *)
-
type comparator = {
-
property: string;
-
is_ascending: bool option; (* Optional, defaults to true *)
-
collation: string option; (* Optional, server-dependent default *)
-
}
-
-
(** PatchObject as per Section 5.3 *)
-
type patch_object = (string * Ezjsonm.value) list
-
-
(** AddedItem structure as per Section 5.6 *)
-
type added_item = {
-
id: id;
-
index: unsigned_int;
-
}
-
-
(** Account object as per Section 1.6.2 *)
-
type account = {
-
name: string;
-
is_personal: bool;
-
is_read_only: bool;
-
account_capabilities: (string * Ezjsonm.value) list;
-
}
-
-
(** Core capability object as per Section 2 *)
-
type core_capability = {
-
max_size_upload: unsigned_int;
-
max_concurrent_upload: unsigned_int;
-
max_size_request: unsigned_int;
-
max_concurrent_requests: unsigned_int;
-
max_calls_in_request: unsigned_int;
-
max_objects_in_get: unsigned_int;
-
max_objects_in_set: unsigned_int;
-
collation_algorithms: string list;
-
}
-
-
(** PushSubscription keys object as per Section 7.2 *)
-
type push_keys = {
-
p256dh: string;
-
auth: string;
-
}
-
-
(** Session object as per Section 2 *)
-
type session = {
-
capabilities: (string * Ezjsonm.value) list;
-
accounts: (id * account) list;
-
primary_accounts: (string * id) list;
-
username: string;
-
api_url: string;
-
download_url: string;
-
upload_url: string;
-
event_source_url: string option;
-
state: string;
-
}
-
-
(** TypeState for state changes as per Section 7.1 *)
-
type type_state = (string * string) list
-
-
(** StateChange object as per Section 7.1 *)
-
type state_change = {
-
changed: (id * type_state) list;
-
}
-
-
(** PushVerification object as per Section 7.2.2 *)
-
type push_verification = {
-
push_subscription_id: id;
-
verification_code: string;
-
}
-
-
(** PushSubscription object as per Section 7.2 *)
-
type push_subscription = {
-
id: id;
-
device_client_id: string;
-
url: string;
-
keys: push_keys option;
-
verification_code: string option;
-
expires: utc_date option;
-
types: string list option;
-
}
-
-
(** Request object as per Section 3.3 *)
-
type request = {
-
using: string list;
-
method_calls: Ezjsonm.value invocation list;
-
created_ids: (id * id) list option;
-
}
-
-
(** Response object as per Section 3.4 *)
-
type response = {
-
method_responses: Ezjsonm.value invocation list;
-
created_ids: (id * id) list option;
-
session_state: string;
-
}
-
-
(** Standard method arguments and responses *)
-
-
(** Arguments for Foo/get method as per Section 5.1 *)
-
type 'a get_arguments = {
-
account_id: id;
-
ids: id list option;
-
properties: string list option;
-
}
-
-
(** Response for Foo/get method as per Section 5.1 *)
-
type 'a get_response = {
-
account_id: id;
-
state: string;
-
list: 'a list;
-
not_found: id list;
-
}
-
-
(** Arguments for Foo/changes method as per Section 5.2 *)
-
type changes_arguments = {
-
account_id: id;
-
since_state: string;
-
max_changes: unsigned_int option;
-
}
-
-
(** Response for Foo/changes method as per Section 5.2 *)
-
type changes_response = {
-
account_id: id;
-
old_state: string;
-
new_state: string;
-
has_more_changes: bool;
-
created: id list;
-
updated: id list;
-
destroyed: id list;
-
}
-
-
(** Arguments for Foo/set method as per Section 5.3 *)
-
type 'a set_arguments = {
-
account_id: id;
-
if_in_state: string option;
-
create: (id * 'a) list option;
-
update: (id * patch_object) list option;
-
destroy: id list option;
-
}
-
-
(** Response for Foo/set method as per Section 5.3 *)
-
type 'a set_response = {
-
account_id: id;
-
old_state: string option;
-
new_state: string;
-
created: (id * 'a) list option;
-
updated: (id * 'a option) list option;
-
destroyed: id list option;
-
not_created: (id * set_error) list option;
-
not_updated: (id * set_error) list option;
-
not_destroyed: (id * set_error) list option;
-
}
-
-
(** Arguments for Foo/copy method as per Section 5.4 *)
-
type 'a copy_arguments = {
-
from_account_id: id;
-
if_from_in_state: string option;
-
account_id: id;
-
if_in_state: string option;
-
create: (id * 'a) list;
-
on_success_destroy_original: bool option;
-
destroy_from_if_in_state: string option;
-
}
-
-
(** Response for Foo/copy method as per Section 5.4 *)
-
type 'a copy_response = {
-
from_account_id: id;
-
account_id: id;
-
old_state: string option;
-
new_state: string;
-
created: (id * 'a) list option;
-
not_created: (id * set_error) list option;
-
}
-
-
(** Arguments for Foo/query method as per Section 5.5 *)
-
type query_arguments = {
-
account_id: id;
-
filter: filter option;
-
sort: comparator list option;
-
position: int_t option;
-
anchor: id option;
-
anchor_offset: int_t option;
-
limit: unsigned_int option;
-
calculate_total: bool option;
-
}
-
-
(** Response for Foo/query method as per Section 5.5 *)
-
type query_response = {
-
account_id: id;
-
query_state: string;
-
can_calculate_changes: bool;
-
position: unsigned_int;
-
ids: id list;
-
total: unsigned_int option;
-
limit: unsigned_int option;
-
}
-
-
(** Arguments for Foo/queryChanges method as per Section 5.6 *)
-
type query_changes_arguments = {
-
account_id: id;
-
filter: filter option;
-
sort: comparator list option;
-
since_query_state: string;
-
max_changes: unsigned_int option;
-
up_to_id: id option;
-
calculate_total: bool option;
-
}
-
-
(** Response for Foo/queryChanges method as per Section 5.6 *)
-
type query_changes_response = {
-
account_id: id;
-
old_query_state: string;
-
new_query_state: string;
-
total: unsigned_int option;
-
removed: id list;
-
added: added_item list option;
-
}
-
-
(** Arguments for Blob/copy method as per Section 6.3 *)
-
type blob_copy_arguments = {
-
from_account_id: id;
-
account_id: id;
-
blob_ids: id list;
-
}
-
-
(** Response for Blob/copy method as per Section 6.3 *)
-
type blob_copy_response = {
-
from_account_id: id;
-
account_id: id;
-
copied: (id * id) list option;
-
not_copied: (id * set_error) list option;
-
}
-
-
(** Upload response as per Section 6.1 *)
-
type upload_response = {
-
account_id: id;
-
blob_id: id;
-
type_: string;
-
size: unsigned_int;
-
}
-
-
(** Problem details object as per RFC7807 and Section 3.6.1 *)
-
type problem_details = {
-
type_: string;
-
status: int option;
-
detail: string option;
-
limit: string option; (* For "limit" error *)
-
}
-
end
-
-
(** Module for working with ResultReferences as described in Section 3.7 of RFC8620 *)
-
module ResultReference = struct
-
open Types
-
-
(** Create a reference to a previous method result *)
-
let create ~result_of ~name ~path =
-
{ result_of; name; path }
-
-
(** Create a JSON pointer path to access a specific property *)
-
let property_path property =
-
"/" ^ property
-
-
(** Create a JSON pointer path to access all items in an array with a specific property *)
-
let array_items_path ?(property="") array_property =
-
let base = "/" ^ array_property ^ "/*" in
-
if property = "" then base
-
else base ^ "/" ^ property
-
-
(** Create argument with result reference.
-
Returns string key prefixed with # and ResultReference value. *)
-
let reference_arg arg_name ref_obj =
-
(* Prefix argument name with # *)
-
let prefixed_name = "#" ^ arg_name in
-
-
(* Convert reference object to JSON *)
-
let json_value = `O [
-
("resultOf", `String ref_obj.result_of);
-
("name", `String ref_obj.name);
-
("path", `String ref_obj.path)
-
] in
-
-
(prefixed_name, json_value)
-
-
(** Create a reference to all IDs returned by a query method *)
-
let query_ids ~result_of =
-
create
-
~result_of
-
~name:"Foo/query"
-
~path:"/ids"
-
-
(** Create a reference to properties of objects returned by a get method *)
-
let get_property ~result_of ~property =
-
create
-
~result_of
-
~name:"Foo/get"
-
~path:("/list/*/" ^ property)
-
end
-
-
module Api = struct
-
open Lwt.Syntax
-
open Types
-
-
(** Error that may occur during API requests *)
-
type error =
-
| Connection_error of string
-
| HTTP_error of int * string
-
| Parse_error of string
-
| Authentication_error
-
-
(** Result type for API operations *)
-
type 'a result = ('a, error) Stdlib.result
-
-
(** Convert an error to a human-readable string *)
-
let string_of_error = function
-
| Connection_error msg -> "Connection error: " ^ msg
-
| HTTP_error (code, body) -> Printf.sprintf "HTTP error %d: %s" code body
-
| Parse_error msg -> "Parse error: " ^ msg
-
| Authentication_error -> "Authentication error"
-
-
(** Pretty-print an error to a formatter *)
-
let pp_error ppf err =
-
Format.fprintf ppf "%s" (string_of_error err)
-
-
(** Configuration for a JMAP API client *)
-
type config = {
-
api_uri: Uri.t;
-
username: string;
-
authentication_token: string;
-
}
-
-
(** Convert Ezjsonm.value to string *)
-
let json_to_string json =
-
Ezjsonm.value_to_string ~minify:false json
-
-
(** Parse response string as JSON value *)
-
let parse_json_string str =
-
try Ok (Ezjsonm.from_string str)
-
with e -> Error (Parse_error (Printexc.to_string e))
-
-
(** Parse JSON response as a JMAP response object *)
-
let parse_response json =
-
try
-
let method_responses =
-
match Ezjsonm.find json ["methodResponses"] with
-
| `A items ->
-
List.map (fun json ->
-
match json with
-
| `A [`String name; args; `String method_call_id] ->
-
{ name; arguments = args; method_call_id }
-
| _ -> raise (Invalid_argument "Invalid invocation format in response")
-
) items
-
| _ -> raise (Invalid_argument "methodResponses is not an array")
-
in
-
let created_ids_opt =
-
try
-
let obj = Ezjsonm.find json ["createdIds"] in
-
match obj with
-
| `O items -> Some (List.map (fun (k, v) ->
-
match v with
-
| `String id -> (k, id)
-
| _ -> raise (Invalid_argument "createdIds value is not a string")
-
) items)
-
| _ -> None
-
with Not_found -> None
-
in
-
let session_state =
-
match Ezjsonm.find json ["sessionState"] with
-
| `String s -> s
-
| _ -> raise (Invalid_argument "sessionState is not a string")
-
in
-
Ok { method_responses; created_ids = created_ids_opt; session_state }
-
with
-
| Not_found -> Error (Parse_error "Required field not found in response")
-
| Invalid_argument msg -> Error (Parse_error msg)
-
| e -> Error (Parse_error (Printexc.to_string e))
-
-
(** Serialize a JMAP request object to JSON *)
-
let serialize_request req =
-
let method_calls_json =
-
`A (List.map (fun (inv : 'a invocation) ->
-
`A [`String inv.name; inv.arguments; `String inv.method_call_id]
-
) req.method_calls)
-
in
-
let using_json = `A (List.map (fun s -> `String s) req.using) in
-
let json = `O [
-
("using", using_json);
-
("methodCalls", method_calls_json)
-
] in
-
let json = match req.created_ids with
-
| Some ids ->
-
let created_ids_json = `O (List.map (fun (k, v) -> (k, `String v)) ids) in
-
Ezjsonm.update json ["createdIds"] (Some created_ids_json)
-
| None -> json
-
in
-
json_to_string json
-
-
(** Make a raw HTTP request *)
-
let make_http_request ~method_ ~headers ~body uri =
-
let open Cohttp in
-
let open Cohttp_lwt_unix in
-
let headers = Header.add_list (Header.init ()) headers in
-
-
(* Print detailed request information to stderr for debugging *)
-
let header_list = Cohttp.Header.to_list headers in
-
let redacted_headers = redact_headers header_list in
-
Logs.info (fun m ->
-
m "\n===== HTTP REQUEST =====\n\
-
URI: %s\n\
-
METHOD: %s\n\
-
HEADERS:\n%s\n\
-
BODY:\n%s\n\
-
======================\n"
-
(Uri.to_string uri)
-
method_
-
(String.concat "\n" (List.map (fun (k, v) -> Printf.sprintf " %s: %s" k v) redacted_headers))
-
body);
-
-
(* Force printing to stderr for immediate debugging *)
-
Printf.eprintf "[DEBUG-REQUEST] URI: %s\n" (Uri.to_string uri);
-
Printf.eprintf "[DEBUG-REQUEST] METHOD: %s\n" method_;
-
Printf.eprintf "[DEBUG-REQUEST] BODY: %s\n%!" body;
-
-
Lwt.catch
-
(fun () ->
-
let* resp, body =
-
match method_ with
-
| "GET" -> Client.get ~headers uri
-
| "POST" -> Client.post ~headers ~body:(Cohttp_lwt.Body.of_string body) uri
-
| _ -> failwith (Printf.sprintf "Unsupported HTTP method: %s" method_)
-
in
-
let* body_str = Cohttp_lwt.Body.to_string body in
-
let status = Response.status resp |> Code.code_of_status in
-
-
(* Print detailed response information to stderr for debugging *)
-
let header_list = Cohttp.Header.to_list (Response.headers resp) in
-
let redacted_headers = redact_headers header_list in
-
Logs.info (fun m ->
-
m "\n===== HTTP RESPONSE =====\n\
-
STATUS: %d\n\
-
HEADERS:\n%s\n\
-
BODY:\n%s\n\
-
======================\n"
-
status
-
(String.concat "\n" (List.map (fun (k, v) -> Printf.sprintf " %s: %s" k v) redacted_headers))
-
body_str);
-
-
(* Force printing to stderr for immediate debugging *)
-
Printf.eprintf "[DEBUG-RESPONSE] STATUS: %d\n" status;
-
Printf.eprintf "[DEBUG-RESPONSE] BODY: %s\n%!" body_str;
-
-
if status >= 200 && status < 300 then
-
Lwt.return (Ok body_str)
-
else
-
Lwt.return (Error (HTTP_error (status, body_str))))
-
(fun e ->
-
let error_msg = Printexc.to_string e in
-
Printf.eprintf "[DEBUG-ERROR] %s\n%!" error_msg;
-
Logs.err (fun m -> m "%s" error_msg);
-
Lwt.return (Error (Connection_error error_msg)))
-
-
(** Make a raw JMAP API request
-
-
TODO:claude *)
-
let make_request config req =
-
let body = serialize_request req in
-
(* Choose appropriate authorization header based on whether it's a bearer token or basic auth *)
-
let auth_header =
-
if String.length config.username > 0 then
-
(* Standard username/password authentication *)
-
"Basic " ^ Base64.encode_string (config.username ^ ":" ^ config.authentication_token)
-
else
-
(* API token (bearer authentication) *)
-
"Bearer " ^ config.authentication_token
-
in
-
-
(* Log auth header at debug level with redaction *)
-
let redacted_header =
-
if String.length config.username > 0 then
-
"Basic " ^ redact_token (Base64.encode_string (config.username ^ ":" ^ config.authentication_token))
-
else
-
"Bearer " ^ redact_token config.authentication_token
-
in
-
Logs.debug (fun m -> m "Using authorization header: %s" redacted_header);
-
-
let headers = [
-
("Content-Type", "application/json");
-
("Content-Length", string_of_int (String.length body));
-
("Authorization", auth_header)
-
] in
-
let* result = make_http_request ~method_:"POST" ~headers ~body config.api_uri in
-
match result with
-
| Ok response_body ->
-
(match parse_json_string response_body with
-
| Ok json ->
-
Logs.debug (fun m -> m "Successfully parsed JSON response");
-
Lwt.return (parse_response json)
-
| Error e ->
-
let msg = match e with Parse_error m -> m | _ -> "unknown error" in
-
Logs.err (fun m -> m "Failed to parse response: %s" msg);
-
Lwt.return (Error e))
-
| Error e ->
-
(match e with
-
| Connection_error msg -> Logs.err (fun m -> m "Connection error: %s" msg)
-
| HTTP_error (code, _) -> Logs.err (fun m -> m "HTTP error %d" code)
-
| Parse_error msg -> Logs.err (fun m -> m "Parse error: %s" msg)
-
| Authentication_error -> Logs.err (fun m -> m "Authentication error"));
-
Lwt.return (Error e)
-
-
(** Parse a JSON object as a Session object *)
-
let parse_session_object json =
-
try
-
let capabilities =
-
match Ezjsonm.find json ["capabilities"] with
-
| `O items -> items
-
| _ -> raise (Invalid_argument "capabilities is not an object")
-
in
-
-
let accounts =
-
match Ezjsonm.find json ["accounts"] with
-
| `O items -> List.map (fun (id, json) ->
-
match json with
-
| `O _ ->
-
let name = Ezjsonm.get_string (Ezjsonm.find json ["name"]) in
-
let is_personal = Ezjsonm.get_bool (Ezjsonm.find json ["isPersonal"]) in
-
let is_read_only = Ezjsonm.get_bool (Ezjsonm.find json ["isReadOnly"]) in
-
let account_capabilities =
-
match Ezjsonm.find json ["accountCapabilities"] with
-
| `O items -> items
-
| _ -> raise (Invalid_argument "accountCapabilities is not an object")
-
in
-
(id, { name; is_personal; is_read_only; account_capabilities })
-
| _ -> raise (Invalid_argument "account value is not an object")
-
) items
-
| _ -> raise (Invalid_argument "accounts is not an object")
-
in
-
-
let primary_accounts =
-
match Ezjsonm.find_opt json ["primaryAccounts"] with
-
| Some (`O items) -> List.map (fun (k, v) ->
-
match v with
-
| `String id -> (k, id)
-
| _ -> raise (Invalid_argument "primaryAccounts value is not a string")
-
) items
-
| Some _ -> raise (Invalid_argument "primaryAccounts is not an object")
-
| None -> []
-
in
-
-
let username = Ezjsonm.get_string (Ezjsonm.find json ["username"]) in
-
let api_url = Ezjsonm.get_string (Ezjsonm.find json ["apiUrl"]) in
-
let download_url = Ezjsonm.get_string (Ezjsonm.find json ["downloadUrl"]) in
-
let upload_url = Ezjsonm.get_string (Ezjsonm.find json ["uploadUrl"]) in
-
let event_source_url =
-
try Some (Ezjsonm.get_string (Ezjsonm.find json ["eventSourceUrl"]))
-
with Not_found -> None
-
in
-
let state = Ezjsonm.get_string (Ezjsonm.find json ["state"]) in
-
-
Ok { capabilities; accounts; primary_accounts; username;
-
api_url; download_url; upload_url; event_source_url; state }
-
with
-
| Not_found -> Error (Parse_error "Required field not found in session object")
-
| Invalid_argument msg -> Error (Parse_error msg)
-
| e -> Error (Parse_error (Printexc.to_string e))
-
-
(** Fetch a Session object from a JMAP server
-
-
TODO:claude *)
-
let get_session uri ?username ?authentication_token ?api_token () =
-
let headers =
-
match (username, authentication_token, api_token) with
-
| (Some u, Some t, _) ->
-
let auth = "Basic " ^ Base64.encode_string (u ^ ":" ^ t) in
-
let redacted_auth = "Basic " ^ redact_token (Base64.encode_string (u ^ ":" ^ t)) in
-
Logs.info (fun m -> m "Session using Basic auth: %s" redacted_auth);
-
[
-
("Content-Type", "application/json");
-
("Authorization", auth)
-
]
-
| (_, _, Some token) ->
-
let auth = "Bearer " ^ token in
-
let redacted_token = redact_token token in
-
Logs.info (fun m -> m "Session using Bearer auth: %s" ("Bearer " ^ redacted_token));
-
[
-
("Content-Type", "application/json");
-
("Authorization", auth)
-
]
-
| _ -> [("Content-Type", "application/json")]
-
in
-
-
let* result = make_http_request ~method_:"GET" ~headers ~body:"" uri in
-
match result with
-
| Ok response_body ->
-
(match parse_json_string response_body with
-
| Ok json ->
-
Logs.debug (fun m -> m "Successfully parsed session response");
-
Lwt.return (parse_session_object json)
-
| Error e ->
-
let msg = match e with Parse_error m -> m | _ -> "unknown error" in
-
Logs.err (fun m -> m "Failed to parse session response: %s" msg);
-
Lwt.return (Error e))
-
| Error e ->
-
let err_msg = match e with
-
| Connection_error msg -> "Connection error: " ^ msg
-
| HTTP_error (code, _) -> Printf.sprintf "HTTP error %d" code
-
| Parse_error msg -> "Parse error: " ^ msg
-
| Authentication_error -> "Authentication error"
-
in
-
Logs.err (fun m -> m "Failed to get session: %s" err_msg);
-
Lwt.return (Error e)
-
-
(** Upload a binary blob to the server
-
-
TODO:claude *)
-
let upload_blob config ~account_id ~content_type data =
-
let upload_url_template = config.api_uri |> Uri.to_string in
-
(* Replace {accountId} with the actual account ID *)
-
let upload_url = Str.global_replace (Str.regexp "{accountId}") account_id upload_url_template in
-
let upload_uri = Uri.of_string upload_url in
-
-
let headers = [
-
("Content-Type", content_type);
-
("Content-Length", string_of_int (String.length data));
-
("Authorization", "Basic " ^ Base64.encode_string (config.username ^ ":" ^ config.authentication_token))
-
] in
-
-
let* result = make_http_request ~method_:"POST" ~headers ~body:data upload_uri in
-
match result with
-
| Ok response_body ->
-
(match parse_json_string response_body with
-
| Ok json ->
-
(try
-
let account_id = Ezjsonm.get_string (Ezjsonm.find json ["accountId"]) in
-
let blob_id = Ezjsonm.get_string (Ezjsonm.find json ["blobId"]) in
-
let type_ = Ezjsonm.get_string (Ezjsonm.find json ["type"]) in
-
let size = Ezjsonm.get_int (Ezjsonm.find json ["size"]) in
-
Lwt.return (Ok { account_id; blob_id; type_; size })
-
with
-
| Not_found -> Lwt.return (Error (Parse_error "Required field not found in upload response"))
-
| e -> Lwt.return (Error (Parse_error (Printexc.to_string e))))
-
| Error e -> Lwt.return (Error e))
-
| Error e -> Lwt.return (Error e)
-
-
(** Download a binary blob from the server
-
-
TODO:claude *)
-
let download_blob config ~account_id ~blob_id ?type_ ?name () =
-
let download_url_template = config.api_uri |> Uri.to_string in
-
-
(* Replace template variables with actual values *)
-
let url = Str.global_replace (Str.regexp "{accountId}") account_id download_url_template in
-
let url = Str.global_replace (Str.regexp "{blobId}") blob_id url in
-
-
let url = match type_ with
-
| Some t -> Str.global_replace (Str.regexp "{type}") (Uri.pct_encode t) url
-
| None -> Str.global_replace (Str.regexp "{type}") "" url
-
in
-
-
let url = match name with
-
| Some n -> Str.global_replace (Str.regexp "{name}") (Uri.pct_encode n) url
-
| None -> Str.global_replace (Str.regexp "{name}") "file" url
-
in
-
-
let download_uri = Uri.of_string url in
-
-
let headers = [
-
("Authorization", "Basic " ^ Base64.encode_string (config.username ^ ":" ^ config.authentication_token))
-
] in
-
-
let* result = make_http_request ~method_:"GET" ~headers ~body:"" download_uri in
-
Lwt.return result
-
end
···
-663
lib/jmap.mli
···
-
(**
-
* JMAP protocol implementation based on RFC8620
-
* https://datatracker.ietf.org/doc/html/rfc8620
-
*
-
* This module implements the core JMAP protocol as defined in RFC8620, providing
-
* types and functions for making JMAP API requests and handling responses.
-
*)
-
-
(** Initialize and configure logging for JMAP
-
@param level Optional logging level (higher means more verbose)
-
@param enable_logs Whether to enable logging at all (default true)
-
@param redact_sensitive Whether to redact sensitive information like tokens (default true)
-
*)
-
val init_logging : ?level:int -> ?enable_logs:bool -> ?redact_sensitive:bool -> unit -> unit
-
-
(** Redact sensitive data like authentication tokens from logs
-
@param redact Whether to perform redaction (default true)
-
@param token The token string to redact
-
@return A redacted version of the token (with characters replaced by '*')
-
*)
-
val redact_token : ?redact:bool -> string -> string
-
-
(** Module for managing JMAP capability URIs and other constants
-
as defined in RFC8620 Section 1.8
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-1.8> RFC8620 Section 1.8
-
*)
-
module Capability : sig
-
(** JMAP core capability URI as specified in RFC8620 Section 2
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-2> RFC8620 Section 2
-
*)
-
val core_uri : string
-
-
(** All JMAP capability types as described in RFC8620 Section 1.8
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-1.8> RFC8620 Section 1.8
-
*)
-
type t =
-
| Core (** Core JMAP capability *)
-
| Extension of string (** Extension capabilities with custom URIs *)
-
-
(** Convert capability to URI string
-
@param capability The capability to convert
-
@return The full URI string for the capability
-
*)
-
val to_string : t -> string
-
-
(** Parse a string to a capability, returns Extension for non-core capabilities
-
@param uri The capability URI string to parse
-
@return The parsed capability type
-
*)
-
val of_string : string -> t
-
-
(** Check if a capability matches the core capability
-
@param capability The capability to check
-
@return True if the capability is the core JMAP capability
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-2>
-
*)
-
val is_core : t -> bool
-
-
(** Check if a capability string is the core capability URI
-
@param uri The capability URI string to check
-
@return True if the string represents the core JMAP capability
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-2>
-
*)
-
val is_core_string : string -> bool
-
-
(** Create a list of capability URI strings
-
@param capabilities List of capability types
-
@return List of capability URI strings
-
*)
-
val strings_of_capabilities : t list -> string list
-
end
-
-
(** {1 Types}
-
Core types as defined in RFC8620
-
@see <https://datatracker.ietf.org/doc/html/rfc8620> RFC8620
-
*)
-
-
module Types : sig
-
(** Id string as defined in RFC8620 Section 1.2.
-
A string of at least 1 and maximum 255 octets, case-sensitive,
-
and does not begin with the '#' character.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-1.2>
-
*)
-
type id = string
-
-
(** Int type bounded within the range -2^53+1 to 2^53-1 as defined in RFC8620 Section 1.3.
-
Represented as JSON number where the value MUST be an integer and in the range.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-1.3>
-
*)
-
type int_t = int
-
-
(** UnsignedInt bounded within the range 0 to 2^53-1 as defined in RFC8620 Section 1.3.
-
Represented as JSON number where the value MUST be a non-negative integer and in the range.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-1.3>
-
*)
-
type unsigned_int = int
-
-
(** Date string in RFC3339 format as defined in RFC8620 Section 1.4.
-
Includes date, time and time zone offset information or UTC.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-1.4>
-
*)
-
type date = string
-
-
(** UTCDate is a Date with 'Z' time zone (UTC) as defined in RFC8620 Section 1.4.
-
Same format as Date type but always with UTC time zone (Z).
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-1.4>
-
*)
-
type utc_date = string
-
-
(** Error object as defined in RFC8620 Section 3.6.2.
-
Used to represent standard error conditions in method responses.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-3.6.2>
-
*)
-
type error = {
-
type_: string; (** The type of error, e.g., "serverFail" *)
-
description: string option; (** Optional human-readable description of the error *)
-
}
-
-
(** Set error object as defined in RFC8620 Section 5.3.
-
Used for reporting errors in set operations.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.3>
-
*)
-
type set_error = {
-
type_: string; (** The type of error, e.g., "notFound" *)
-
description: string option; (** Optional human-readable description of the error *)
-
properties: string list option; (** Properties causing the error, if applicable *)
-
existing_id: id option; (** For "alreadyExists" error, the ID of the existing object *)
-
}
-
-
(** Invocation object as defined in RFC8620 Section 3.2.
-
Represents a method call in the JMAP protocol.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-3.2>
-
*)
-
type 'a invocation = {
-
name: string; (** The name of the method to call, e.g., "Mailbox/get" *)
-
arguments: 'a; (** The arguments for the method, type varies by method *)
-
method_call_id: string; (** Client-specified ID for referencing this call *)
-
}
-
-
(** ResultReference object as defined in RFC8620 Section 3.7.
-
Used to reference results from previous method calls.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-3.7>
-
*)
-
type result_reference = {
-
result_of: string; (** The method_call_id of the method to reference *)
-
name: string; (** Name of the response in the referenced result *)
-
path: string; (** JSON pointer path to the value being referenced *)
-
}
-
-
(** FilterOperator, FilterCondition and Filter as defined in RFC8620 Section 5.5.
-
Used for complex filtering in query methods.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.5>
-
*)
-
type filter_operator = {
-
operator: string; (** The operator: "AND", "OR", "NOT" *)
-
conditions: filter list; (** The conditions to apply the operator to *)
-
}
-
-
(** Property/value pairs for filtering *)
-
and filter_condition =
-
(string * Ezjsonm.value) list
-
-
and filter =
-
| Operator of filter_operator (** Logical operator combining conditions *)
-
| Condition of filter_condition (** Simple property-based condition *)
-
-
(** Comparator object for sorting as defined in RFC8620 Section 5.5.
-
Specifies how to sort query results.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.5>
-
*)
-
type comparator = {
-
property: string; (** The property to sort by *)
-
is_ascending: bool option; (** Sort order (true for ascending, false for descending) *)
-
collation: string option; (** Collation algorithm for string comparison *)
-
}
-
-
(** PatchObject as defined in RFC8620 Section 5.3.
-
Used to represent a set of updates to apply to an object.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.3>
-
*)
-
type patch_object = (string * Ezjsonm.value) list (** List of property/value pairs to update *)
-
-
(** AddedItem structure as defined in RFC8620 Section 5.6.
-
Represents an item added to a query result.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.6>
-
*)
-
type added_item = {
-
id: id; (** The ID of the added item *)
-
index: unsigned_int; (** The index in the result list where the item appears *)
-
}
-
-
(** Account object as defined in RFC8620 Section 1.6.2.
-
Represents a user account in JMAP.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-1.6.2>
-
*)
-
type account = {
-
name: string; (** User-friendly account name, e.g. "john@example.com" *)
-
is_personal: bool; (** Whether this account belongs to the authenticated user *)
-
is_read_only: bool; (** Whether this account can be modified *)
-
account_capabilities: (string * Ezjsonm.value) list; (** Capabilities available for this account *)
-
}
-
-
(** Core capability object as defined in RFC8620 Section 2.
-
Describes limits and features of the JMAP server.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-2>
-
*)
-
type core_capability = {
-
max_size_upload: unsigned_int; (** Maximum file size in octets for uploads *)
-
max_concurrent_upload: unsigned_int; (** Maximum number of concurrent uploads *)
-
max_size_request: unsigned_int; (** Maximum size in octets for a request *)
-
max_concurrent_requests: unsigned_int; (** Maximum number of concurrent requests *)
-
max_calls_in_request: unsigned_int; (** Maximum number of method calls in a request *)
-
max_objects_in_get: unsigned_int; (** Maximum number of objects in a get request *)
-
max_objects_in_set: unsigned_int; (** Maximum number of objects in a set request *)
-
collation_algorithms: string list; (** Supported string collation algorithms *)
-
}
-
-
(** PushSubscription keys object as defined in RFC8620 Section 7.2.
-
Contains encryption keys for web push subscriptions.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-7.2>
-
*)
-
type push_keys = {
-
p256dh: string; (** User agent public key (Base64url-encoded) *)
-
auth: string; (** Authentication secret (Base64url-encoded) *)
-
}
-
-
(** Session object as defined in RFC8620 Section 2.
-
Contains information about the server and user's accounts.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-2>
-
*)
-
type session = {
-
capabilities: (string * Ezjsonm.value) list; (** Server capabilities with their properties *)
-
accounts: (id * account) list; (** Map of account IDs to account objects *)
-
primary_accounts: (string * id) list; (** Map of capability URIs to primary account IDs *)
-
username: string; (** Username associated with this session *)
-
api_url: string; (** URL to use for JMAP API requests *)
-
download_url: string; (** URL endpoint to download files *)
-
upload_url: string; (** URL endpoint to upload files *)
-
event_source_url: string option; (** URL for Server-Sent Events notifications *)
-
state: string; (** String representing the state on the server *)
-
}
-
-
(** TypeState for state changes as defined in RFC8620 Section 7.1.
-
Maps data type names to the state string for that type.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-7.1>
-
*)
-
type type_state = (string * string) list (** (data type name, state string) pairs *)
-
-
(** StateChange object as defined in RFC8620 Section 7.1.
-
Represents changes to data types for different accounts.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-7.1>
-
*)
-
type state_change = {
-
changed: (id * type_state) list; (** Map of account IDs to type state changes *)
-
}
-
-
(** PushVerification object as defined in RFC8620 Section 7.2.2.
-
Used for verifying push subscription ownership.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-7.2.2>
-
*)
-
type push_verification = {
-
push_subscription_id: id; (** ID of the push subscription being verified *)
-
verification_code: string; (** Code the client must submit to verify ownership *)
-
}
-
-
(** PushSubscription object as defined in RFC8620 Section 7.2.
-
Represents a subscription for push notifications.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-7.2>
-
*)
-
type push_subscription = {
-
id: id; (** Server-assigned ID for the subscription *)
-
device_client_id: string; (** ID representing the client/device *)
-
url: string; (** URL to which events are pushed *)
-
keys: push_keys option; (** Encryption keys for web push, if any *)
-
verification_code: string option; (** Verification code if not yet verified *)
-
expires: utc_date option; (** When the subscription expires, if applicable *)
-
types: string list option; (** Types of changes to push, null means all *)
-
}
-
-
(** Request object as defined in RFC8620 Section 3.3.
-
Represents a JMAP request from client to server.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-3.3>
-
*)
-
type request = {
-
using: string list; (** Capabilities required for this request *)
-
method_calls: Ezjsonm.value invocation list; (** List of method calls to process *)
-
created_ids: (id * id) list option; (** Map of client-created IDs to server IDs *)
-
}
-
-
(** Response object as defined in RFC8620 Section 3.4.
-
Represents a JMAP response from server to client.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-3.4>
-
*)
-
type response = {
-
method_responses: Ezjsonm.value invocation list; (** List of method responses *)
-
created_ids: (id * id) list option; (** Map of client-created IDs to server IDs *)
-
session_state: string; (** Current session state on the server *)
-
}
-
-
(** {2 Standard method arguments and responses}
-
Standard method patterns defined in RFC8620 Section 5
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5>
-
*)
-
-
(** Arguments for Foo/get method as defined in RFC8620 Section 5.1.
-
Generic template for retrieving objects by ID.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.1>
-
*)
-
type 'a get_arguments = {
-
account_id: id; (** The account ID to operate on *)
-
ids: id list option; (** IDs to fetch, null means all *)
-
properties: string list option; (** Properties to return, null means all *)
-
}
-
-
(** Response for Foo/get method as defined in RFC8620 Section 5.1.
-
Generic template for returning requested objects.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.1>
-
*)
-
type 'a get_response = {
-
account_id: id; (** The account ID that was operated on *)
-
state: string; (** Server state for the type at the time of processing *)
-
list: 'a list; (** The list of requested objects *)
-
not_found: id list; (** IDs that could not be found *)
-
}
-
-
(** Arguments for Foo/changes method as defined in RFC8620 Section 5.2.
-
Generic template for getting state changes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.2>
-
*)
-
type changes_arguments = {
-
account_id: id; (** The account ID to operate on *)
-
since_state: string; (** The last state seen by the client *)
-
max_changes: unsigned_int option; (** Maximum number of changes to return *)
-
}
-
-
(** Response for Foo/changes method as defined in RFC8620 Section 5.2.
-
Generic template for returning object changes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.2>
-
*)
-
type changes_response = {
-
account_id: id; (** The account ID that was operated on *)
-
old_state: string; (** The state provided in the request *)
-
new_state: string; (** The current server state *)
-
has_more_changes: bool; (** True if more changes are available *)
-
created: id list; (** IDs of objects created since old_state *)
-
updated: id list; (** IDs of objects updated since old_state *)
-
destroyed: id list; (** IDs of objects destroyed since old_state *)
-
}
-
-
(** Arguments for Foo/set method as defined in RFC8620 Section 5.3.
-
Generic template for creating, updating, and destroying objects.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.3>
-
*)
-
type 'a set_arguments = {
-
account_id: id; (** The account ID to operate on *)
-
if_in_state: string option; (** Only apply changes if in this state *)
-
create: (id * 'a) list option; (** Map of creation IDs to objects to create *)
-
update: (id * patch_object) list option; (** Map of IDs to patches to apply *)
-
destroy: id list option; (** List of IDs to destroy *)
-
}
-
-
(** Response for Foo/set method as defined in RFC8620 Section 5.3.
-
Generic template for reporting create/update/destroy status.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.3>
-
*)
-
type 'a set_response = {
-
account_id: id; (** The account ID that was operated on *)
-
old_state: string option; (** The state before processing, if changed *)
-
new_state: string; (** The current server state *)
-
created: (id * 'a) list option; (** Map of creation IDs to created objects *)
-
updated: (id * 'a option) list option; (** Map of IDs to updated objects *)
-
destroyed: id list option; (** List of IDs successfully destroyed *)
-
not_created: (id * set_error) list option; (** Map of IDs to errors for failed creates *)
-
not_updated: (id * set_error) list option; (** Map of IDs to errors for failed updates *)
-
not_destroyed: (id * set_error) list option; (** Map of IDs to errors for failed destroys *)
-
}
-
-
(** Arguments for Foo/copy method as defined in RFC8620 Section 5.4.
-
Generic template for copying objects between accounts.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.4>
-
*)
-
type 'a copy_arguments = {
-
from_account_id: id; (** The account ID to copy from *)
-
if_from_in_state: string option; (** Only copy if source account in this state *)
-
account_id: id; (** The account ID to copy to *)
-
if_in_state: string option; (** Only copy if destination account in this state *)
-
create: (id * 'a) list; (** Map of creation IDs to objects to copy *)
-
on_success_destroy_original: bool option; (** Whether to destroy the original after copying *)
-
destroy_from_if_in_state: string option; (** Only destroy originals if in this state *)
-
}
-
-
(** Response for Foo/copy method as defined in RFC8620 Section 5.4.
-
Generic template for reporting copy operation status.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.4>
-
*)
-
type 'a copy_response = {
-
from_account_id: id; (** The account ID that was copied from *)
-
account_id: id; (** The account ID that was copied to *)
-
old_state: string option; (** The state before processing, if changed *)
-
new_state: string; (** The current server state *)
-
created: (id * 'a) list option; (** Map of creation IDs to created objects *)
-
not_created: (id * set_error) list option; (** Map of IDs to errors for failed copies *)
-
}
-
-
(** Arguments for Foo/query method as defined in RFC8620 Section 5.5.
-
Generic template for querying objects.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.5>
-
*)
-
type query_arguments = {
-
account_id: id; (** The account ID to operate on *)
-
filter: filter option; (** Filter to determine which objects are returned *)
-
sort: comparator list option; (** Sort order for returned objects *)
-
position: int_t option; (** Zero-based index of first result to return *)
-
anchor: id option; (** ID of object to use as reference point *)
-
anchor_offset: int_t option; (** Offset from anchor to start returning results *)
-
limit: unsigned_int option; (** Maximum number of results to return *)
-
calculate_total: bool option; (** Whether to calculate the total number of matching objects *)
-
}
-
-
(** Response for Foo/query method as defined in RFC8620 Section 5.5.
-
Generic template for returning query results.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.5>
-
*)
-
type query_response = {
-
account_id: id; (** The account ID that was operated on *)
-
query_state: string; (** State string for the query results *)
-
can_calculate_changes: bool; (** Whether queryChanges can be used with these results *)
-
position: unsigned_int; (** Zero-based index of the first result *)
-
ids: id list; (** The list of IDs for objects matching the query *)
-
total: unsigned_int option; (** Total number of matching objects, if calculated *)
-
limit: unsigned_int option; (** Limit enforced on the results, if requested *)
-
}
-
-
(** Arguments for Foo/queryChanges method as defined in RFC8620 Section 5.6.
-
Generic template for getting query result changes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.6>
-
*)
-
type query_changes_arguments = {
-
account_id: id; (** The account ID to operate on *)
-
filter: filter option; (** Same filter as used in the original query *)
-
sort: comparator list option; (** Same sort as used in the original query *)
-
since_query_state: string; (** The query_state from previous results *)
-
max_changes: unsigned_int option; (** Maximum number of changes to return *)
-
up_to_id: id option; (** Only calculate changes until this ID is encountered *)
-
calculate_total: bool option; (** Whether to recalculate the total matches *)
-
}
-
-
(** Response for Foo/queryChanges method as defined in RFC8620 Section 5.6.
-
Generic template for returning query result changes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-5.6>
-
*)
-
type query_changes_response = {
-
account_id: id; (** The account ID that was operated on *)
-
old_query_state: string; (** The query_state from the request *)
-
new_query_state: string; (** The current query_state on the server *)
-
total: unsigned_int option; (** Updated total number of matches, if calculated *)
-
removed: id list; (** IDs that were in the old results but not in the new *)
-
added: added_item list option; (** IDs that are in the new results but not the old *)
-
}
-
-
(** Arguments for Blob/copy method as defined in RFC8620 Section 6.3.
-
Used for copying binary data between accounts.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-6.3>
-
*)
-
type blob_copy_arguments = {
-
from_account_id: id; (** The account ID to copy blobs from *)
-
account_id: id; (** The account ID to copy blobs to *)
-
blob_ids: id list; (** IDs of blobs to copy *)
-
}
-
-
(** Response for Blob/copy method as defined in RFC8620 Section 6.3.
-
Reports the results of copying binary data.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-6.3>
-
*)
-
type blob_copy_response = {
-
from_account_id: id; (** The account ID that was copied from *)
-
account_id: id; (** The account ID that was copied to *)
-
copied: (id * id) list option; (** Map of source IDs to destination IDs *)
-
not_copied: (id * set_error) list option; (** Map of IDs to errors for failed copies *)
-
}
-
-
(** Upload response as defined in RFC8620 Section 6.1.
-
Contains information about an uploaded binary blob.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-6.1>
-
*)
-
type upload_response = {
-
account_id: id; (** The account ID the blob was uploaded to *)
-
blob_id: id; (** The ID for the uploaded blob *)
-
type_: string; (** Media type of the blob *)
-
size: unsigned_int; (** Size of the blob in octets *)
-
}
-
-
(** Problem details object as defined in RFC8620 Section 3.6.1 and RFC7807.
-
Used for HTTP error responses in the JMAP protocol.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-3.6.1>
-
@see <https://datatracker.ietf.org/doc/html/rfc7807>
-
*)
-
type problem_details = {
-
type_: string; (** URI that identifies the problem type *)
-
status: int option; (** HTTP status code for this problem *)
-
detail: string option; (** Human-readable explanation of the problem *)
-
limit: string option; (** For "limit" errors, which limit was exceeded *)
-
}
-
end
-
-
(** {1 API Client}
-
Modules for interacting with JMAP servers
-
*)
-
-
(** Module for working with ResultReferences as described in Section 3.7 of RFC8620.
-
Provides utilities to create and compose results from previous methods.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-3.7>
-
*)
-
module ResultReference : sig
-
(** Create a reference to a previous method result
-
@param result_of The methodCallId of the method call to reference
-
@param name The name in the response to reference (e.g., "list")
-
@param path JSON pointer path to the value being referenced
-
@return A result_reference object
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-3.7>
-
*)
-
val create :
-
result_of:string ->
-
name:string ->
-
path:string ->
-
Types.result_reference
-
-
(** Create a JSON pointer path to access a specific property
-
@param property The property name to access
-
@return A JSON pointer path string
-
*)
-
val property_path : string -> string
-
-
(** Create a JSON pointer path to access all items in an array with a specific property
-
@param property Optional property to access within each array item
-
@param array_name The name of the array to access
-
@return A JSON pointer path string that references all items in the array
-
*)
-
val array_items_path : ?property:string -> string -> string
-
-
(** Create argument with result reference.
-
@param arg_name The name of the argument
-
@param reference The result reference to use
-
@return A tuple of string key (with # prefix) and ResultReference JSON value
-
*)
-
val reference_arg : string -> Types.result_reference -> string * Ezjsonm.value
-
-
(** Create a reference to all IDs returned by a query method
-
@param result_of The methodCallId of the query method call
-
@return A result_reference to the IDs returned by the query
-
*)
-
val query_ids :
-
result_of:string ->
-
Types.result_reference
-
-
(** Create a reference to properties of objects returned by a get method
-
@param result_of The methodCallId of the get method call
-
@param property The property to reference in the returned objects
-
@return A result_reference to the specified property in the get results
-
*)
-
val get_property :
-
result_of:string ->
-
property:string ->
-
Types.result_reference
-
end
-
-
(** Module for making JMAP API requests over HTTP.
-
Provides functionality to interact with JMAP servers according to RFC8620.
-
@see <https://datatracker.ietf.org/doc/html/rfc8620>
-
*)
-
module Api : sig
-
(** Error that may occur during API requests *)
-
type error =
-
| Connection_error of string (** Network-related errors *)
-
| HTTP_error of int * string (** HTTP errors with status code and message *)
-
| Parse_error of string (** JSON parsing errors *)
-
| Authentication_error (** Authentication failures *)
-
-
(** Result type for API operations *)
-
type 'a result = ('a, error) Stdlib.result
-
-
(** Convert an error to a human-readable string
-
@param err The error to convert
-
@return A string representation of the error
-
*)
-
val string_of_error : error -> string
-
-
(** Pretty-print an error to a formatter
-
@param ppf The formatter to print to
-
@param err The error to print
-
*)
-
val pp_error : Format.formatter -> error -> unit
-
-
(** Configuration for a JMAP API client as defined in RFC8620 Section 3.1
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-3.1>
-
*)
-
type config = {
-
api_uri: Uri.t; (** The JMAP API endpoint URI *)
-
username: string; (** The username for authentication *)
-
authentication_token: string; (** The token for authentication *)
-
}
-
-
(** Make a raw JMAP API request as defined in RFC8620 Section 3.3
-
@param config The API client configuration
-
@param request The JMAP request to send
-
@return A result containing the JMAP response or an error
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-3.3>
-
*)
-
val make_request :
-
config ->
-
Types.request ->
-
Types.response result Lwt.t
-
-
(** Fetch a Session object from a JMAP server as defined in RFC8620 Section 2
-
Can authenticate with either username/password or API token.
-
@param uri The URI of the JMAP session resource
-
@param username Optional username for authentication
-
@param authentication_token Optional password or token for authentication
-
@param api_token Optional API token for Bearer authentication
-
@return A result containing the session object or an error
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-2>
-
*)
-
val get_session :
-
Uri.t ->
-
?username:string ->
-
?authentication_token:string ->
-
?api_token:string ->
-
unit ->
-
Types.session result Lwt.t
-
-
(** Upload a binary blob to the server as defined in RFC8620 Section 6.1
-
@param config The API client configuration
-
@param account_id The account ID to upload to
-
@param content_type The MIME type of the blob
-
@param data The blob data as a string
-
@return A result containing the upload response or an error
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-6.1>
-
*)
-
val upload_blob :
-
config ->
-
account_id:Types.id ->
-
content_type:string ->
-
string ->
-
Types.upload_response result Lwt.t
-
-
(** Download a binary blob from the server as defined in RFC8620 Section 6.2
-
@param config The API client configuration
-
@param account_id The account ID that contains the blob
-
@param blob_id The ID of the blob to download
-
@param type_ Optional MIME type to require for the blob
-
@param name Optional name for the downloaded blob
-
@return A result containing the blob data as a string or an error
-
@see <https://datatracker.ietf.org/doc/html/rfc8620#section-6.2>
-
*)
-
val download_blob :
-
config ->
-
account_id:Types.id ->
-
blob_id:Types.id ->
-
?type_:string ->
-
?name:string ->
-
unit ->
-
string result Lwt.t
-
end
···
-2828
lib/jmap_mail.ml
···
-
(** Implementation of the JMAP Mail extension, as defined in RFC8621 *)
-
-
(** Module for managing JMAP Mail-specific capability URIs *)
-
module Capability = struct
-
(** Mail capability URI *)
-
let mail_uri = "urn:ietf:params:jmap:mail"
-
-
(** Submission capability URI *)
-
let submission_uri = "urn:ietf:params:jmap:submission"
-
-
(** Vacation response capability URI *)
-
let vacation_response_uri = "urn:ietf:params:jmap:vacationresponse"
-
-
(** All mail extension capability types *)
-
type t =
-
| Mail (** Mail capability *)
-
| Submission (** Submission capability *)
-
| VacationResponse (** Vacation response capability *)
-
| Extension of string (** Custom extension *)
-
-
(** Convert capability to URI string *)
-
let to_string = function
-
| Mail -> mail_uri
-
| Submission -> submission_uri
-
| VacationResponse -> vacation_response_uri
-
| Extension s -> s
-
-
(** Parse a string to a capability *)
-
let of_string s =
-
if s = mail_uri then Mail
-
else if s = submission_uri then Submission
-
else if s = vacation_response_uri then VacationResponse
-
else Extension s
-
-
(** Check if a capability is a standard mail capability *)
-
let is_standard = function
-
| Mail | Submission | VacationResponse -> true
-
| Extension _ -> false
-
-
(** Check if a capability string is a standard mail capability *)
-
let is_standard_string s =
-
s = mail_uri || s = submission_uri || s = vacation_response_uri
-
-
(** Create a list of capability strings *)
-
let strings_of_capabilities capabilities =
-
List.map to_string capabilities
-
end
-
-
module Types = struct
-
open Jmap.Types
-
-
(** {1 Mail capabilities} *)
-
-
(** Capability URI for JMAP Mail*)
-
let capability_mail = Capability.mail_uri
-
-
(** Capability URI for JMAP Submission *)
-
let capability_submission = Capability.submission_uri
-
-
(** Capability URI for JMAP Vacation Response *)
-
let capability_vacation_response = Capability.vacation_response_uri
-
-
(** {1:mailbox Mailbox objects} *)
-
-
(** A role for a mailbox. See RFC8621 Section 2. *)
-
type mailbox_role =
-
| All (** All mail *)
-
| Archive (** Archived mail *)
-
| Drafts (** Draft messages *)
-
| Flagged (** Starred/flagged mail *)
-
| Important (** Important mail *)
-
| Inbox (** Inbox *)
-
| Junk (** Spam/Junk mail *)
-
| Sent (** Sent mail *)
-
| Trash (** Deleted/Trash mail *)
-
| Unknown of string (** Server-specific roles *)
-
-
(** A mailbox (folder) in a mail account. See RFC8621 Section 2. *)
-
type mailbox = {
-
id : id;
-
name : string;
-
parent_id : id option;
-
role : mailbox_role option;
-
sort_order : unsigned_int;
-
total_emails : unsigned_int;
-
unread_emails : unsigned_int;
-
total_threads : unsigned_int;
-
unread_threads : unsigned_int;
-
is_subscribed : bool;
-
my_rights : mailbox_rights;
-
}
-
-
(** Rights for a mailbox. See RFC8621 Section 2. *)
-
and mailbox_rights = {
-
may_read_items : bool;
-
may_add_items : bool;
-
may_remove_items : bool;
-
may_set_seen : bool;
-
may_set_keywords : bool;
-
may_create_child : bool;
-
may_rename : bool;
-
may_delete : bool;
-
may_submit : bool;
-
}
-
-
(** Filter condition for mailbox queries. See RFC8621 Section 2.3. *)
-
type mailbox_filter_condition = {
-
parent_id : id option;
-
name : string option;
-
role : string option;
-
has_any_role : bool option;
-
is_subscribed : bool option;
-
}
-
-
type mailbox_query_filter = [
-
| `And of mailbox_query_filter list
-
| `Or of mailbox_query_filter list
-
| `Not of mailbox_query_filter
-
| `Condition of mailbox_filter_condition
-
]
-
-
(** Mailbox/get request arguments. See RFC8621 Section 2.1. *)
-
type mailbox_get_arguments = {
-
account_id : id;
-
ids : id list option;
-
properties : string list option;
-
}
-
-
(** Mailbox/get response. See RFC8621 Section 2.1. *)
-
type mailbox_get_response = {
-
account_id : id;
-
state : string;
-
list : mailbox list;
-
not_found : id list;
-
}
-
-
(** Mailbox/changes request arguments. See RFC8621 Section 2.2. *)
-
type mailbox_changes_arguments = {
-
account_id : id;
-
since_state : string;
-
max_changes : unsigned_int option;
-
}
-
-
(** Mailbox/changes response. See RFC8621 Section 2.2. *)
-
type mailbox_changes_response = {
-
account_id : id;
-
old_state : string;
-
new_state : string;
-
has_more_changes : bool;
-
created : id list;
-
updated : id list;
-
destroyed : id list;
-
}
-
-
(** Mailbox/query request arguments. See RFC8621 Section 2.3. *)
-
type mailbox_query_arguments = {
-
account_id : id;
-
filter : mailbox_query_filter option;
-
sort : [ `name | `role | `sort_order ] list option;
-
limit : unsigned_int option;
-
}
-
-
(** Mailbox/query response. See RFC8621 Section 2.3. *)
-
type mailbox_query_response = {
-
account_id : id;
-
query_state : string;
-
can_calculate_changes : bool;
-
position : unsigned_int;
-
ids : id list;
-
total : unsigned_int option;
-
}
-
-
(** Mailbox/queryChanges request arguments. See RFC8621 Section 2.4. *)
-
type mailbox_query_changes_arguments = {
-
account_id : id;
-
filter : mailbox_query_filter option;
-
sort : [ `name | `role | `sort_order ] list option;
-
since_query_state : string;
-
max_changes : unsigned_int option;
-
up_to_id : id option;
-
}
-
-
(** Mailbox/queryChanges response. See RFC8621 Section 2.4. *)
-
type mailbox_query_changes_response = {
-
account_id : id;
-
old_query_state : string;
-
new_query_state : string;
-
total : unsigned_int option;
-
removed : id list;
-
added : mailbox_query_changes_added list;
-
}
-
-
and mailbox_query_changes_added = {
-
id : id;
-
index : unsigned_int;
-
}
-
-
(** Mailbox/set request arguments. See RFC8621 Section 2.5. *)
-
type mailbox_set_arguments = {
-
account_id : id;
-
if_in_state : string option;
-
create : (id * mailbox_creation) list option;
-
update : (id * mailbox_update) list option;
-
destroy : id list option;
-
}
-
-
and mailbox_creation = {
-
name : string;
-
parent_id : id option;
-
role : string option;
-
sort_order : unsigned_int option;
-
is_subscribed : bool option;
-
}
-
-
and mailbox_update = {
-
name : string option;
-
parent_id : id option;
-
role : string option;
-
sort_order : unsigned_int option;
-
is_subscribed : bool option;
-
}
-
-
(** Mailbox/set response. See RFC8621 Section 2.5. *)
-
type mailbox_set_response = {
-
account_id : id;
-
old_state : string option;
-
new_state : string;
-
created : (id * mailbox) list option;
-
updated : id list option;
-
destroyed : id list option;
-
not_created : (id * set_error) list option;
-
not_updated : (id * set_error) list option;
-
not_destroyed : (id * set_error) list option;
-
}
-
-
(** {1:thread Thread objects} *)
-
-
(** A thread in a mail account. See RFC8621 Section 3. *)
-
type thread = {
-
id : id;
-
email_ids : id list;
-
}
-
-
(** Thread/get request arguments. See RFC8621 Section 3.1. *)
-
type thread_get_arguments = {
-
account_id : id;
-
ids : id list option;
-
properties : string list option;
-
}
-
-
(** Thread/get response. See RFC8621 Section 3.1. *)
-
type thread_get_response = {
-
account_id : id;
-
state : string;
-
list : thread list;
-
not_found : id list;
-
}
-
-
(** Thread/changes request arguments. See RFC8621 Section 3.2. *)
-
type thread_changes_arguments = {
-
account_id : id;
-
since_state : string;
-
max_changes : unsigned_int option;
-
}
-
-
(** Thread/changes response. See RFC8621 Section 3.2. *)
-
type thread_changes_response = {
-
account_id : id;
-
old_state : string;
-
new_state : string;
-
has_more_changes : bool;
-
created : id list;
-
updated : id list;
-
destroyed : id list;
-
}
-
-
(** {1:email Email objects} *)
-
-
(** Addressing (mailbox) information. See RFC8621 Section 4.1.1. *)
-
type email_address = {
-
name : string option;
-
email : string;
-
parameters : (string * string) list;
-
}
-
-
(** Message header field. See RFC8621 Section 4.1.2. *)
-
type header = {
-
name : string;
-
value : string;
-
}
-
-
(** Email keyword (flag). See RFC8621 Section 4.3. *)
-
type keyword =
-
| Flagged
-
| Answered
-
| Draft
-
| Forwarded
-
| Phishing
-
| Junk
-
| NotJunk
-
| Seen
-
| Unread
-
| Custom of string
-
-
(** Email message. See RFC8621 Section 4. *)
-
type email = {
-
id : id;
-
blob_id : id;
-
thread_id : id;
-
mailbox_ids : (id * bool) list;
-
keywords : (keyword * bool) list;
-
size : unsigned_int;
-
received_at : utc_date;
-
message_id : string list;
-
in_reply_to : string list option;
-
references : string list option;
-
sender : email_address list option;
-
from : email_address list option;
-
to_ : email_address list option;
-
cc : email_address list option;
-
bcc : email_address list option;
-
reply_to : email_address list option;
-
subject : string option;
-
sent_at : utc_date option;
-
has_attachment : bool option;
-
preview : string option;
-
body_values : (string * string) list option;
-
text_body : email_body_part list option;
-
html_body : email_body_part list option;
-
attachments : email_body_part list option;
-
headers : header list option;
-
}
-
-
(** Email body part. See RFC8621 Section 4.1.4. *)
-
and email_body_part = {
-
part_id : string option;
-
blob_id : id option;
-
size : unsigned_int option;
-
headers : header list option;
-
name : string option;
-
type_ : string option;
-
charset : string option;
-
disposition : string option;
-
cid : string option;
-
language : string list option;
-
location : string option;
-
sub_parts : email_body_part list option;
-
header_parameter_name : string option;
-
header_parameter_value : string option;
-
}
-
-
(** Email query filter condition. See RFC8621 Section 4.4. *)
-
type email_filter_condition = {
-
in_mailbox : id option;
-
in_mailbox_other_than : id list option;
-
min_size : unsigned_int option;
-
max_size : unsigned_int option;
-
before : utc_date option;
-
after : utc_date option;
-
header : (string * string) option;
-
from : string option;
-
to_ : string option;
-
cc : string option;
-
bcc : string option;
-
subject : string option;
-
body : string option;
-
has_keyword : string option;
-
not_keyword : string option;
-
has_attachment : bool option;
-
text : string option;
-
}
-
-
type email_query_filter = [
-
| `And of email_query_filter list
-
| `Or of email_query_filter list
-
| `Not of email_query_filter
-
| `Condition of email_filter_condition
-
]
-
-
(** Email/get request arguments. See RFC8621 Section 4.5. *)
-
type email_get_arguments = {
-
account_id : id;
-
ids : id list option;
-
properties : string list option;
-
body_properties : string list option;
-
fetch_text_body_values : bool option;
-
fetch_html_body_values : bool option;
-
fetch_all_body_values : bool option;
-
max_body_value_bytes : unsigned_int option;
-
}
-
-
(** Email/get response. See RFC8621 Section 4.5. *)
-
type email_get_response = {
-
account_id : id;
-
state : string;
-
list : email list;
-
not_found : id list;
-
}
-
-
(** Email/changes request arguments. See RFC8621 Section 4.6. *)
-
type email_changes_arguments = {
-
account_id : id;
-
since_state : string;
-
max_changes : unsigned_int option;
-
}
-
-
(** Email/changes response. See RFC8621 Section 4.6. *)
-
type email_changes_response = {
-
account_id : id;
-
old_state : string;
-
new_state : string;
-
has_more_changes : bool;
-
created : id list;
-
updated : id list;
-
destroyed : id list;
-
}
-
-
(** Email/query request arguments. See RFC8621 Section 4.4. *)
-
type email_query_arguments = {
-
account_id : id;
-
filter : email_query_filter option;
-
sort : comparator list option;
-
collapse_threads : bool option;
-
position : unsigned_int option;
-
anchor : id option;
-
anchor_offset : int_t option;
-
limit : unsigned_int option;
-
calculate_total : bool option;
-
}
-
-
(** Email/query response. See RFC8621 Section 4.4. *)
-
type email_query_response = {
-
account_id : id;
-
query_state : string;
-
can_calculate_changes : bool;
-
position : unsigned_int;
-
ids : id list;
-
total : unsigned_int option;
-
thread_ids : id list option;
-
}
-
-
(** Email/queryChanges request arguments. See RFC8621 Section 4.7. *)
-
type email_query_changes_arguments = {
-
account_id : id;
-
filter : email_query_filter option;
-
sort : comparator list option;
-
collapse_threads : bool option;
-
since_query_state : string;
-
max_changes : unsigned_int option;
-
up_to_id : id option;
-
}
-
-
(** Email/queryChanges response. See RFC8621 Section 4.7. *)
-
type email_query_changes_response = {
-
account_id : id;
-
old_query_state : string;
-
new_query_state : string;
-
total : unsigned_int option;
-
removed : id list;
-
added : email_query_changes_added list;
-
}
-
-
and email_query_changes_added = {
-
id : id;
-
index : unsigned_int;
-
}
-
-
(** Email/set request arguments. See RFC8621 Section 4.8. *)
-
type email_set_arguments = {
-
account_id : id;
-
if_in_state : string option;
-
create : (id * email_creation) list option;
-
update : (id * email_update) list option;
-
destroy : id list option;
-
}
-
-
and email_creation = {
-
mailbox_ids : (id * bool) list;
-
keywords : (keyword * bool) list option;
-
received_at : utc_date option;
-
message_id : string list option;
-
in_reply_to : string list option;
-
references : string list option;
-
sender : email_address list option;
-
from : email_address list option;
-
to_ : email_address list option;
-
cc : email_address list option;
-
bcc : email_address list option;
-
reply_to : email_address list option;
-
subject : string option;
-
body_values : (string * string) list option;
-
text_body : email_body_part list option;
-
html_body : email_body_part list option;
-
attachments : email_body_part list option;
-
headers : header list option;
-
}
-
-
and email_update = {
-
keywords : (keyword * bool) list option;
-
mailbox_ids : (id * bool) list option;
-
}
-
-
(** Email/set response. See RFC8621 Section 4.8. *)
-
type email_set_response = {
-
account_id : id;
-
old_state : string option;
-
new_state : string;
-
created : (id * email) list option;
-
updated : id list option;
-
destroyed : id list option;
-
not_created : (id * set_error) list option;
-
not_updated : (id * set_error) list option;
-
not_destroyed : (id * set_error) list option;
-
}
-
-
(** Email/copy request arguments. See RFC8621 Section 4.9. *)
-
type email_copy_arguments = {
-
from_account_id : id;
-
account_id : id;
-
create : (id * email_creation) list;
-
on_success_destroy_original : bool option;
-
}
-
-
(** Email/copy response. See RFC8621 Section 4.9. *)
-
type email_copy_response = {
-
from_account_id : id;
-
account_id : id;
-
created : (id * email) list option;
-
not_created : (id * set_error) list option;
-
}
-
-
(** Email/import request arguments. See RFC8621 Section 4.10. *)
-
type email_import_arguments = {
-
account_id : id;
-
emails : (id * email_import) list;
-
}
-
-
and email_import = {
-
blob_id : id;
-
mailbox_ids : (id * bool) list;
-
keywords : (keyword * bool) list option;
-
received_at : utc_date option;
-
}
-
-
(** Email/import response. See RFC8621 Section 4.10. *)
-
type email_import_response = {
-
account_id : id;
-
created : (id * email) list option;
-
not_created : (id * set_error) list option;
-
}
-
-
(** {1:search_snippet Search snippets} *)
-
-
(** SearchSnippet/get request arguments. See RFC8621 Section 4.11. *)
-
type search_snippet_get_arguments = {
-
account_id : id;
-
email_ids : id list;
-
filter : email_filter_condition;
-
}
-
-
(** SearchSnippet/get response. See RFC8621 Section 4.11. *)
-
type search_snippet_get_response = {
-
account_id : id;
-
list : (id * search_snippet) list;
-
not_found : id list;
-
}
-
-
and search_snippet = {
-
subject : string option;
-
preview : string option;
-
}
-
-
(** {1:submission EmailSubmission objects} *)
-
-
(** EmailSubmission address. See RFC8621 Section 5.1. *)
-
type submission_address = {
-
email : string;
-
parameters : (string * string) list option;
-
}
-
-
(** Email submission object. See RFC8621 Section 5.1. *)
-
type email_submission = {
-
id : id;
-
identity_id : id;
-
email_id : id;
-
thread_id : id;
-
envelope : envelope option;
-
send_at : utc_date option;
-
undo_status : [
-
| `pending
-
| `final
-
| `canceled
-
] option;
-
delivery_status : (string * submission_status) list option;
-
dsn_blob_ids : (string * id) list option;
-
mdn_blob_ids : (string * id) list option;
-
}
-
-
(** Envelope for mail submission. See RFC8621 Section 5.1. *)
-
and envelope = {
-
mail_from : submission_address;
-
rcpt_to : submission_address list;
-
}
-
-
(** Delivery status for submitted email. See RFC8621 Section 5.1. *)
-
and submission_status = {
-
smtp_reply : string;
-
delivered : string option;
-
}
-
-
(** EmailSubmission/get request arguments. See RFC8621 Section 5.3. *)
-
type email_submission_get_arguments = {
-
account_id : id;
-
ids : id list option;
-
properties : string list option;
-
}
-
-
(** EmailSubmission/get response. See RFC8621 Section 5.3. *)
-
type email_submission_get_response = {
-
account_id : id;
-
state : string;
-
list : email_submission list;
-
not_found : id list;
-
}
-
-
(** EmailSubmission/changes request arguments. See RFC8621 Section 5.4. *)
-
type email_submission_changes_arguments = {
-
account_id : id;
-
since_state : string;
-
max_changes : unsigned_int option;
-
}
-
-
(** EmailSubmission/changes response. See RFC8621 Section 5.4. *)
-
type email_submission_changes_response = {
-
account_id : id;
-
old_state : string;
-
new_state : string;
-
has_more_changes : bool;
-
created : id list;
-
updated : id list;
-
destroyed : id list;
-
}
-
-
(** EmailSubmission/query filter condition. See RFC8621 Section 5.5. *)
-
type email_submission_filter_condition = {
-
identity_id : id option;
-
email_id : id option;
-
thread_id : id option;
-
before : utc_date option;
-
after : utc_date option;
-
subject : string option;
-
}
-
-
type email_submission_query_filter = [
-
| `And of email_submission_query_filter list
-
| `Or of email_submission_query_filter list
-
| `Not of email_submission_query_filter
-
| `Condition of email_submission_filter_condition
-
]
-
-
(** EmailSubmission/query request arguments. See RFC8621 Section 5.5. *)
-
type email_submission_query_arguments = {
-
account_id : id;
-
filter : email_submission_query_filter option;
-
sort : comparator list option;
-
position : unsigned_int option;
-
anchor : id option;
-
anchor_offset : int_t option;
-
limit : unsigned_int option;
-
calculate_total : bool option;
-
}
-
-
(** EmailSubmission/query response. See RFC8621 Section 5.5. *)
-
type email_submission_query_response = {
-
account_id : id;
-
query_state : string;
-
can_calculate_changes : bool;
-
position : unsigned_int;
-
ids : id list;
-
total : unsigned_int option;
-
}
-
-
(** EmailSubmission/set request arguments. See RFC8621 Section 5.6. *)
-
type email_submission_set_arguments = {
-
account_id : id;
-
if_in_state : string option;
-
create : (id * email_submission_creation) list option;
-
update : (id * email_submission_update) list option;
-
destroy : id list option;
-
on_success_update_email : (id * email_update) list option;
-
}
-
-
and email_submission_creation = {
-
email_id : id;
-
identity_id : id;
-
envelope : envelope option;
-
send_at : utc_date option;
-
}
-
-
and email_submission_update = {
-
email_id : id option;
-
identity_id : id option;
-
envelope : envelope option;
-
undo_status : [`canceled] option;
-
}
-
-
(** EmailSubmission/set response. See RFC8621 Section 5.6. *)
-
type email_submission_set_response = {
-
account_id : id;
-
old_state : string option;
-
new_state : string;
-
created : (id * email_submission) list option;
-
updated : id list option;
-
destroyed : id list option;
-
not_created : (id * set_error) list option;
-
not_updated : (id * set_error) list option;
-
not_destroyed : (id * set_error) list option;
-
}
-
-
(** {1:identity Identity objects} *)
-
-
(** Identity for sending mail. See RFC8621 Section 6. *)
-
type identity = {
-
id : id;
-
name : string;
-
email : string;
-
reply_to : email_address list option;
-
bcc : email_address list option;
-
text_signature : string option;
-
html_signature : string option;
-
may_delete : bool;
-
}
-
-
(** Identity/get request arguments. See RFC8621 Section 6.1. *)
-
type identity_get_arguments = {
-
account_id : id;
-
ids : id list option;
-
properties : string list option;
-
}
-
-
(** Identity/get response. See RFC8621 Section 6.1. *)
-
type identity_get_response = {
-
account_id : id;
-
state : string;
-
list : identity list;
-
not_found : id list;
-
}
-
-
(** Identity/changes request arguments. See RFC8621 Section 6.2. *)
-
type identity_changes_arguments = {
-
account_id : id;
-
since_state : string;
-
max_changes : unsigned_int option;
-
}
-
-
(** Identity/changes response. See RFC8621 Section 6.2. *)
-
type identity_changes_response = {
-
account_id : id;
-
old_state : string;
-
new_state : string;
-
has_more_changes : bool;
-
created : id list;
-
updated : id list;
-
destroyed : id list;
-
}
-
-
(** Identity/set request arguments. See RFC8621 Section 6.3. *)
-
type identity_set_arguments = {
-
account_id : id;
-
if_in_state : string option;
-
create : (id * identity_creation) list option;
-
update : (id * identity_update) list option;
-
destroy : id list option;
-
}
-
-
and identity_creation = {
-
name : string;
-
email : string;
-
reply_to : email_address list option;
-
bcc : email_address list option;
-
text_signature : string option;
-
html_signature : string option;
-
}
-
-
and identity_update = {
-
name : string option;
-
email : string option;
-
reply_to : email_address list option;
-
bcc : email_address list option;
-
text_signature : string option;
-
html_signature : string option;
-
}
-
-
(** Identity/set response. See RFC8621 Section 6.3. *)
-
type identity_set_response = {
-
account_id : id;
-
old_state : string option;
-
new_state : string;
-
created : (id * identity) list option;
-
updated : id list option;
-
destroyed : id list option;
-
not_created : (id * set_error) list option;
-
not_updated : (id * set_error) list option;
-
not_destroyed : (id * set_error) list option;
-
}
-
-
(** {1:vacation_response VacationResponse objects} *)
-
-
(** Vacation auto-reply setting. See RFC8621 Section 7. *)
-
type vacation_response = {
-
id : id;
-
is_enabled : bool;
-
from_date : utc_date option;
-
to_date : utc_date option;
-
subject : string option;
-
text_body : string option;
-
html_body : string option;
-
}
-
-
(** VacationResponse/get request arguments. See RFC8621 Section 7.2. *)
-
type vacation_response_get_arguments = {
-
account_id : id;
-
ids : id list option;
-
properties : string list option;
-
}
-
-
(** VacationResponse/get response. See RFC8621 Section 7.2. *)
-
type vacation_response_get_response = {
-
account_id : id;
-
state : string;
-
list : vacation_response list;
-
not_found : id list;
-
}
-
-
(** VacationResponse/set request arguments. See RFC8621 Section 7.3. *)
-
type vacation_response_set_arguments = {
-
account_id : id;
-
if_in_state : string option;
-
update : (id * vacation_response_update) list;
-
}
-
-
and vacation_response_update = {
-
is_enabled : bool option;
-
from_date : utc_date option;
-
to_date : utc_date option;
-
subject : string option;
-
text_body : string option;
-
html_body : string option;
-
}
-
-
(** VacationResponse/set response. See RFC8621 Section 7.3. *)
-
type vacation_response_set_response = {
-
account_id : id;
-
old_state : string option;
-
new_state : string;
-
updated : id list option;
-
not_updated : (id * set_error) list option;
-
}
-
-
(** {1:message_flags Message Flags and Mailbox Attributes} *)
-
-
(** Flag color defined by the combination of MailFlagBit0, MailFlagBit1, and MailFlagBit2 keywords *)
-
type flag_color =
-
| Red (** Bit pattern 000 *)
-
| Orange (** Bit pattern 100 *)
-
| Yellow (** Bit pattern 010 *)
-
| Green (** Bit pattern 111 *)
-
| Blue (** Bit pattern 001 *)
-
| Purple (** Bit pattern 101 *)
-
| Gray (** Bit pattern 011 *)
-
-
(** Standard message keywords as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 *)
-
type message_keyword =
-
| Notify (** Indicate a notification should be shown for this message *)
-
| Muted (** User is not interested in future replies to this thread *)
-
| Followed (** User is particularly interested in future replies to this thread *)
-
| Memo (** Message is a note-to-self about another message in the same thread *)
-
| HasMemo (** Message has an associated memo with the $memo keyword *)
-
| HasAttachment (** Message has an attachment *)
-
| HasNoAttachment (** Message does not have an attachment *)
-
| AutoSent (** Message was sent automatically as a response due to a user rule *)
-
| Unsubscribed (** User has unsubscribed from the thread this message is in *)
-
| CanUnsubscribe (** Message has an RFC8058-compliant List-Unsubscribe header *)
-
| Imported (** Message was imported from another mailbox *)
-
| IsTrusted (** Server has verified authenticity of the from name and email *)
-
| MaskedEmail (** Message was received via an alias created for an individual sender *)
-
| New (** Message should be made more prominent due to a recent action *)
-
| MailFlagBit0 (** Bit 0 of the 3-bit flag color pattern *)
-
| MailFlagBit1 (** Bit 1 of the 3-bit flag color pattern *)
-
| MailFlagBit2 (** Bit 2 of the 3-bit flag color pattern *)
-
| OtherKeyword of string (** Other non-standard keywords *)
-
-
(** Special mailbox attribute names as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 *)
-
type mailbox_attribute =
-
| Snoozed (** Mailbox containing messages that have been snoozed *)
-
| Scheduled (** Mailbox containing messages scheduled to be sent later *)
-
| Memos (** Mailbox containing messages with the $memo keyword *)
-
| OtherAttribute of string (** Other non-standard mailbox attributes *)
-
-
(** Functions for working with flag colors based on the specification in
-
draft-ietf-mailmaint-messageflag-mailboxattribute-02, section 3.1. *)
-
-
(** Convert bit pattern to flag color *)
-
let flag_color_of_bits bit0 bit1 bit2 =
-
match (bit0, bit1, bit2) with
-
| (false, false, false) -> Red (* 000 *)
-
| (true, false, false) -> Orange (* 100 *)
-
| (false, true, false) -> Yellow (* 010 *)
-
| (true, true, true) -> Green (* 111 *)
-
| (false, false, true) -> Blue (* 001 *)
-
| (true, false, true) -> Purple (* 101 *)
-
| (false, true, true) -> Gray (* 011 *)
-
| (true, true, false) -> Green (* 110 - not in spec, defaulting to green *)
-
-
(** Get bits for a flag color *)
-
let bits_of_flag_color = function
-
| Red -> (false, false, false)
-
| Orange -> (true, false, false)
-
| Yellow -> (false, true, false)
-
| Green -> (true, true, true)
-
| Blue -> (false, false, true)
-
| Purple -> (true, false, true)
-
| Gray -> (false, true, true)
-
-
(** Check if a keyword list contains a flag color *)
-
let has_flag_color keywords =
-
let has_bit0 = List.exists (function
-
| (Custom s, true) when s = "$MailFlagBit0" -> true
-
| _ -> false
-
) keywords in
-
-
let has_bit1 = List.exists (function
-
| (Custom s, true) when s = "$MailFlagBit1" -> true
-
| _ -> false
-
) keywords in
-
-
let has_bit2 = List.exists (function
-
| (Custom s, true) when s = "$MailFlagBit2" -> true
-
| _ -> false
-
) keywords in
-
-
has_bit0 || has_bit1 || has_bit2
-
-
(** Extract flag color from keywords if present *)
-
let get_flag_color keywords =
-
(* First check if the message has the \Flagged system flag *)
-
let is_flagged = List.exists (function
-
| (Flagged, true) -> true
-
| _ -> false
-
) keywords in
-
-
if not is_flagged then
-
None
-
else
-
(* Get values of each bit flag *)
-
let bit0 = List.exists (function
-
| (Custom s, true) when s = "$MailFlagBit0" -> true
-
| _ -> false
-
) keywords in
-
-
let bit1 = List.exists (function
-
| (Custom s, true) when s = "$MailFlagBit1" -> true
-
| _ -> false
-
) keywords in
-
-
let bit2 = List.exists (function
-
| (Custom s, true) when s = "$MailFlagBit2" -> true
-
| _ -> false
-
) keywords in
-
-
Some (flag_color_of_bits bit0 bit1 bit2)
-
-
(** Convert a message keyword to its string representation *)
-
let string_of_message_keyword = function
-
| Notify -> "$notify"
-
| Muted -> "$muted"
-
| Followed -> "$followed"
-
| Memo -> "$memo"
-
| HasMemo -> "$hasmemo"
-
| HasAttachment -> "$hasattachment"
-
| HasNoAttachment -> "$hasnoattachment"
-
| AutoSent -> "$autosent"
-
| Unsubscribed -> "$unsubscribed"
-
| CanUnsubscribe -> "$canunsubscribe"
-
| Imported -> "$imported"
-
| IsTrusted -> "$istrusted"
-
| MaskedEmail -> "$maskedemail"
-
| New -> "$new"
-
| MailFlagBit0 -> "$MailFlagBit0"
-
| MailFlagBit1 -> "$MailFlagBit1"
-
| MailFlagBit2 -> "$MailFlagBit2"
-
| OtherKeyword s -> s
-
-
(** Parse a string into a message keyword *)
-
let message_keyword_of_string = function
-
| "$notify" -> Notify
-
| "$muted" -> Muted
-
| "$followed" -> Followed
-
| "$memo" -> Memo
-
| "$hasmemo" -> HasMemo
-
| "$hasattachment" -> HasAttachment
-
| "$hasnoattachment" -> HasNoAttachment
-
| "$autosent" -> AutoSent
-
| "$unsubscribed" -> Unsubscribed
-
| "$canunsubscribe" -> CanUnsubscribe
-
| "$imported" -> Imported
-
| "$istrusted" -> IsTrusted
-
| "$maskedemail" -> MaskedEmail
-
| "$new" -> New
-
| "$MailFlagBit0" -> MailFlagBit0
-
| "$MailFlagBit1" -> MailFlagBit1
-
| "$MailFlagBit2" -> MailFlagBit2
-
| s -> OtherKeyword s
-
-
(** Convert a mailbox attribute to its string representation *)
-
let string_of_mailbox_attribute = function
-
| Snoozed -> "Snoozed"
-
| Scheduled -> "Scheduled"
-
| Memos -> "Memos"
-
| OtherAttribute s -> s
-
-
(** Parse a string into a mailbox attribute *)
-
let mailbox_attribute_of_string = function
-
| "Snoozed" -> Snoozed
-
| "Scheduled" -> Scheduled
-
| "Memos" -> Memos
-
| s -> OtherAttribute s
-
-
(** Get a human-readable representation of a flag color *)
-
let human_readable_flag_color = function
-
| Red -> "Red"
-
| Orange -> "Orange"
-
| Yellow -> "Yellow"
-
| Green -> "Green"
-
| Blue -> "Blue"
-
| Purple -> "Purple"
-
| Gray -> "Gray"
-
-
(** Get a human-readable representation of a message keyword *)
-
let human_readable_message_keyword = function
-
| Notify -> "Notify"
-
| Muted -> "Muted"
-
| Followed -> "Followed"
-
| Memo -> "Memo"
-
| HasMemo -> "Has Memo"
-
| HasAttachment -> "Has Attachment"
-
| HasNoAttachment -> "No Attachment"
-
| AutoSent -> "Auto Sent"
-
| Unsubscribed -> "Unsubscribed"
-
| CanUnsubscribe -> "Can Unsubscribe"
-
| Imported -> "Imported"
-
| IsTrusted -> "Trusted"
-
| MaskedEmail -> "Masked Email"
-
| New -> "New"
-
| MailFlagBit0 | MailFlagBit1 | MailFlagBit2 -> "Flag Bit"
-
| OtherKeyword s -> s
-
-
(** Format email keywords into a human-readable string representation *)
-
let format_email_keywords keywords =
-
(* Get flag color if present *)
-
let color_str =
-
match get_flag_color keywords with
-
| Some color -> human_readable_flag_color color
-
| None -> ""
-
in
-
-
(* Get standard JMAP keywords *)
-
let standard_keywords = List.filter_map (fun (kw, active) ->
-
if not active then None
-
else match kw with
-
| Flagged -> Some "Flagged"
-
| Answered -> Some "Answered"
-
| Draft -> Some "Draft"
-
| Forwarded -> Some "Forwarded"
-
| Phishing -> Some "Phishing"
-
| Junk -> Some "Junk"
-
| NotJunk -> Some "Not Junk"
-
| Seen -> Some "Seen"
-
| Unread -> Some "Unread"
-
| _ -> None
-
) keywords in
-
-
(* Get message keywords *)
-
let message_keywords = List.filter_map (fun (kw, active) ->
-
if not active then None
-
else match kw with
-
| Custom s ->
-
(* Try to parse as message keyword *)
-
let message_kw = message_keyword_of_string s in
-
(match message_kw with
-
| OtherKeyword _ -> None
-
| MailFlagBit0 | MailFlagBit1 | MailFlagBit2 -> None
-
| kw -> Some (human_readable_message_keyword kw))
-
| _ -> None
-
) keywords in
-
-
(* Combine all human-readable labels *)
-
let all_parts =
-
(if color_str <> "" then [color_str] else []) @
-
standard_keywords @
-
message_keywords
-
in
-
-
String.concat ", " all_parts
-
end
-
-
(** {1 JSON serialization} *)
-
-
module Json = struct
-
open Types
-
-
(** {2 Helper functions for serialization} *)
-
-
let string_of_mailbox_role = function
-
| All -> "all"
-
| Archive -> "archive"
-
| Drafts -> "drafts"
-
| Flagged -> "flagged"
-
| Important -> "important"
-
| Inbox -> "inbox"
-
| Junk -> "junk"
-
| Sent -> "sent"
-
| Trash -> "trash"
-
| Unknown s -> s
-
-
let mailbox_role_of_string = function
-
| "all" -> All
-
| "archive" -> Archive
-
| "drafts" -> Drafts
-
| "flagged" -> Flagged
-
| "important" -> Important
-
| "inbox" -> Inbox
-
| "junk" -> Junk
-
| "sent" -> Sent
-
| "trash" -> Trash
-
| s -> Unknown s
-
-
let string_of_keyword = function
-
| Flagged -> "$flagged"
-
| Answered -> "$answered"
-
| Draft -> "$draft"
-
| Forwarded -> "$forwarded"
-
| Phishing -> "$phishing"
-
| Junk -> "$junk"
-
| NotJunk -> "$notjunk"
-
| Seen -> "$seen"
-
| Unread -> "$unread"
-
| Custom s -> s
-
-
let keyword_of_string = function
-
| "$flagged" -> Flagged
-
| "$answered" -> Answered
-
| "$draft" -> Draft
-
| "$forwarded" -> Forwarded
-
| "$phishing" -> Phishing
-
| "$junk" -> Junk
-
| "$notjunk" -> NotJunk
-
| "$seen" -> Seen
-
| "$unread" -> Unread
-
| s -> Custom s
-
-
(** {2 Mailbox serialization} *)
-
-
(** TODO:claude - Need to implement all JSON serialization functions
-
for each type we've defined. This would be a substantial amount of
-
code and likely require additional understanding of the ezjsonm API.
-
-
For a full implementation, we would need functions to convert between
-
OCaml types and JSON for each of:
-
- mailbox, mailbox_rights, mailbox query/update operations
-
- thread operations
-
- email, email_address, header, email_body_part
-
- email query/update operations
-
- submission operations
-
- identity operations
-
- vacation response operations
-
*)
-
end
-
-
(** {1 API functions} *)
-
-
open Lwt.Syntax
-
open Jmap.Api
-
open Jmap.Types
-
-
(** Authentication credentials for a JMAP server *)
-
type credentials = {
-
username: string;
-
password: string;
-
}
-
-
(** Connection to a JMAP mail server *)
-
type connection = {
-
session: Jmap.Types.session;
-
config: Jmap.Api.config;
-
}
-
-
(** Convert JSON mail object to OCaml type *)
-
let mailbox_of_json json =
-
try
-
let open Ezjsonm in
-
let id = get_string (find json ["id"]) in
-
let name = get_string (find json ["name"]) in
-
(* Handle parentId which can be null *)
-
let parent_id =
-
match find_opt json ["parentId"] with
-
| Some (`Null) -> None
-
| Some (`String s) -> Some s
-
| None -> None
-
| _ -> None
-
in
-
(* Handle role which might be null *)
-
let role =
-
match find_opt json ["role"] with
-
| Some (`Null) -> None
-
| Some (`String s) -> Some (Json.mailbox_role_of_string s)
-
| None -> None
-
| _ -> None
-
in
-
let sort_order = get_int (find json ["sortOrder"]) in
-
let total_emails = get_int (find json ["totalEmails"]) in
-
let unread_emails = get_int (find json ["unreadEmails"]) in
-
let total_threads = get_int (find json ["totalThreads"]) in
-
let unread_threads = get_int (find json ["unreadThreads"]) in
-
let is_subscribed = get_bool (find json ["isSubscribed"]) in
-
let rights_json = find json ["myRights"] in
-
let my_rights = {
-
Types.may_read_items = get_bool (find rights_json ["mayReadItems"]);
-
may_add_items = get_bool (find rights_json ["mayAddItems"]);
-
may_remove_items = get_bool (find rights_json ["mayRemoveItems"]);
-
may_set_seen = get_bool (find rights_json ["maySetSeen"]);
-
may_set_keywords = get_bool (find rights_json ["maySetKeywords"]);
-
may_create_child = get_bool (find rights_json ["mayCreateChild"]);
-
may_rename = get_bool (find rights_json ["mayRename"]);
-
may_delete = get_bool (find rights_json ["mayDelete"]);
-
may_submit = get_bool (find rights_json ["maySubmit"]);
-
} in
-
let result = {
-
Types.id;
-
name;
-
parent_id;
-
role;
-
sort_order;
-
total_emails;
-
unread_emails;
-
total_threads;
-
unread_threads;
-
is_subscribed;
-
my_rights;
-
} in
-
Ok (result)
-
with
-
| Not_found ->
-
Error (Parse_error "Required field not found in mailbox object")
-
| Invalid_argument msg ->
-
Error (Parse_error msg)
-
| e ->
-
Error (Parse_error (Printexc.to_string e))
-
-
(** Convert JSON email object to OCaml type *)
-
let email_of_json json =
-
try
-
let open Ezjsonm in
-
-
let id = get_string (find json ["id"]) in
-
let blob_id = get_string (find json ["blobId"]) in
-
let thread_id = get_string (find json ["threadId"]) in
-
-
(* Process mailboxIds map *)
-
let mailbox_ids_json = find json ["mailboxIds"] in
-
let mailbox_ids = match mailbox_ids_json with
-
| `O items -> List.map (fun (id, v) -> (id, get_bool v)) items
-
| _ -> raise (Invalid_argument "mailboxIds is not an object")
-
in
-
-
(* Process keywords map *)
-
let keywords_json = find json ["keywords"] in
-
let keywords = match keywords_json with
-
| `O items -> List.map (fun (k, v) ->
-
(Json.keyword_of_string k, get_bool v)) items
-
| _ -> raise (Invalid_argument "keywords is not an object")
-
in
-
-
let size = get_int (find json ["size"]) in
-
let received_at = get_string (find json ["receivedAt"]) in
-
-
(* Handle messageId which might be an array or missing *)
-
let message_id =
-
match find_opt json ["messageId"] with
-
| Some (`A ids) -> List.map (fun id ->
-
match id with
-
| `String s -> s
-
| _ -> raise (Invalid_argument "messageId item is not a string")
-
) ids
-
| Some (`String s) -> [s] (* Handle single string case *)
-
| None -> [] (* Handle missing case *)
-
| _ -> raise (Invalid_argument "messageId has unexpected type")
-
in
-
-
(* Parse optional fields *)
-
let parse_email_addresses opt_json =
-
match opt_json with
-
| Some (`A items) ->
-
Some (List.map (fun addr_json ->
-
let name =
-
match find_opt addr_json ["name"] with
-
| Some (`String s) -> Some s
-
| Some (`Null) -> None
-
| None -> None
-
| _ -> None
-
in
-
let email = get_string (find addr_json ["email"]) in
-
let parameters =
-
match find_opt addr_json ["parameters"] with
-
| Some (`O items) -> List.map (fun (k, v) ->
-
match v with
-
| `String s -> (k, s)
-
| _ -> (k, "")
-
) items
-
| _ -> []
-
in
-
{ Types.name; email; parameters }
-
) items)
-
| _ -> None
-
in
-
-
(* Handle optional string arrays with null handling *)
-
let parse_string_array_opt field_name =
-
match find_opt json [field_name] with
-
| Some (`A ids) ->
-
Some (List.filter_map (function
-
| `String s -> Some s
-
| _ -> None
-
) ids)
-
| Some (`Null) -> None
-
| None -> None
-
| _ -> None
-
in
-
-
let in_reply_to = parse_string_array_opt "inReplyTo" in
-
let references = parse_string_array_opt "references" in
-
-
let sender = parse_email_addresses (find_opt json ["sender"]) in
-
let from = parse_email_addresses (find_opt json ["from"]) in
-
let to_ = parse_email_addresses (find_opt json ["to"]) in
-
let cc = parse_email_addresses (find_opt json ["cc"]) in
-
let bcc = parse_email_addresses (find_opt json ["bcc"]) in
-
let reply_to = parse_email_addresses (find_opt json ["replyTo"]) in
-
-
(* Handle optional string fields with null handling *)
-
let parse_string_opt field_name =
-
match find_opt json [field_name] with
-
| Some (`String s) -> Some s
-
| Some (`Null) -> None
-
| None -> None
-
| _ -> None
-
in
-
-
let subject = parse_string_opt "subject" in
-
let sent_at = parse_string_opt "sentAt" in
-
-
(* Handle optional boolean fields with null handling *)
-
let parse_bool_opt field_name =
-
match find_opt json [field_name] with
-
| Some (`Bool b) -> Some b
-
| Some (`Null) -> None
-
| None -> None
-
| _ -> None
-
in
-
-
let has_attachment = parse_bool_opt "hasAttachment" in
-
let preview = parse_string_opt "preview" in
-
-
(* TODO Body parts parsing would go here - omitting for brevity *)
-
Ok ({
-
Types.id;
-
blob_id;
-
thread_id;
-
mailbox_ids;
-
keywords;
-
size;
-
received_at;
-
message_id;
-
in_reply_to;
-
references;
-
sender;
-
from;
-
to_;
-
cc;
-
bcc;
-
reply_to;
-
subject;
-
sent_at;
-
has_attachment;
-
preview;
-
body_values = None;
-
text_body = None;
-
html_body = None;
-
attachments = None;
-
headers = None;
-
})
-
with
-
| Not_found ->
-
Error (Parse_error "Required field not found in email object")
-
| Invalid_argument msg ->
-
Error (Parse_error msg)
-
| e ->
-
Error (Parse_error (Printexc.to_string e))
-
-
(** Login to a JMAP server and establish a connection
-
@param uri The URI of the JMAP server
-
@param credentials Authentication credentials
-
@return A connection object if successful
-
-
TODO:claude *)
-
let login ~uri ~credentials =
-
let* session_result = get_session (Uri.of_string uri)
-
~username:credentials.username
-
~authentication_token:credentials.password
-
() in
-
match session_result with
-
| Ok session ->
-
let api_uri = Uri.of_string session.api_url in
-
let config = {
-
api_uri;
-
username = credentials.username;
-
authentication_token = credentials.password;
-
} in
-
Lwt.return (Ok { session; config })
-
| Error e -> Lwt.return (Error e)
-
-
(** Login to a JMAP server using an API token
-
@param uri The URI of the JMAP server
-
@param api_token The API token for authentication
-
@return A connection object if successful
-
-
TODO:claude *)
-
let login_with_token ~uri ~api_token =
-
let* session_result = get_session (Uri.of_string uri)
-
~api_token
-
() in
-
match session_result with
-
| Ok session ->
-
let api_uri = Uri.of_string session.api_url in
-
let config = {
-
api_uri;
-
username = ""; (* Empty username indicates we're using token auth *)
-
authentication_token = api_token;
-
} in
-
Lwt.return (Ok { session; config })
-
| Error e -> Lwt.return (Error e)
-
-
(** Get all mailboxes for an account
-
@param conn The JMAP connection
-
@param account_id The account ID to get mailboxes for
-
@return A list of mailboxes if successful
-
-
TODO:claude *)
-
let get_mailboxes conn ~account_id =
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail
-
];
-
method_calls = [
-
{
-
name = "Mailbox/get";
-
arguments = `O [
-
("accountId", `String account_id);
-
];
-
method_call_id = "m1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* response_result = make_request conn.config request in
-
match response_result with
-
| Ok response ->
-
let result =
-
try
-
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Mailbox/get") response.method_responses in
-
let args = method_response.arguments in
-
match Ezjsonm.find_opt args ["list"] with
-
| Some (`A mailbox_list) ->
-
let parse_results = List.map mailbox_of_json mailbox_list in
-
let (successes, failures) = List.partition Result.is_ok parse_results in
-
if List.length failures > 0 then
-
Error (Parse_error "Failed to parse some mailboxes")
-
else
-
Ok (List.map Result.get_ok successes)
-
| _ -> Error (Parse_error "Mailbox list not found in response")
-
with
-
| Not_found -> Error (Parse_error "Mailbox/get method response not found")
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
Lwt.return result
-
| Error e -> Lwt.return (Error e)
-
-
(** Get a specific mailbox by ID
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param mailbox_id The mailbox ID to retrieve
-
@return The mailbox if found
-
-
TODO:claude *)
-
let get_mailbox conn ~account_id ~mailbox_id =
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail
-
];
-
method_calls = [
-
{
-
name = "Mailbox/get";
-
arguments = `O [
-
("accountId", `String account_id);
-
("ids", `A [`String mailbox_id]);
-
];
-
method_call_id = "m1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* response_result = make_request conn.config request in
-
match response_result with
-
| Ok response ->
-
let result =
-
try
-
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Mailbox/get") response.method_responses in
-
let args = method_response.arguments in
-
match Ezjsonm.find_opt args ["list"] with
-
| Some (`A [mailbox]) -> mailbox_of_json mailbox
-
| Some (`A []) -> Error (Parse_error ("Mailbox not found: " ^ mailbox_id))
-
| _ -> Error (Parse_error "Expected single mailbox in response")
-
with
-
| Not_found -> Error (Parse_error "Mailbox/get method response not found")
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
Lwt.return result
-
| Error e -> Lwt.return (Error e)
-
-
(** Get messages in a mailbox
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param mailbox_id The mailbox ID to get messages from
-
@param limit Optional limit on number of messages to return
-
@return The list of email messages if successful
-
-
TODO:claude *)
-
let get_messages_in_mailbox conn ~account_id ~mailbox_id ?limit () =
-
(* First query the emails in the mailbox *)
-
let query_request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail
-
];
-
method_calls = [
-
{
-
name = "Email/query";
-
arguments = `O ([
-
("accountId", `String account_id);
-
("filter", `O [("inMailbox", `String mailbox_id)]);
-
("sort", `A [`O [("property", `String "receivedAt"); ("isAscending", `Bool false)]]);
-
] @ (match limit with
-
| Some l -> [("limit", `Float (float_of_int l))]
-
| None -> []
-
));
-
method_call_id = "q1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* query_result = make_request conn.config query_request in
-
match query_result with
-
| Ok query_response ->
-
(try
-
let query_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Email/query") query_response.method_responses in
-
let args = query_method.arguments in
-
match Ezjsonm.find_opt args ["ids"] with
-
| Some (`A ids) ->
-
let email_ids = List.map (function
-
| `String id -> id
-
| _ -> raise (Invalid_argument "Email ID is not a string")
-
) ids in
-
-
(* If we have IDs, fetch the actual email objects *)
-
if List.length email_ids > 0 then
-
let get_request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail
-
];
-
method_calls = [
-
{
-
name = "Email/get";
-
arguments = `O [
-
("accountId", `String account_id);
-
("ids", `A (List.map (fun id -> `String id) email_ids));
-
];
-
method_call_id = "g1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* get_result = make_request conn.config get_request in
-
match get_result with
-
| Ok get_response ->
-
(try
-
let get_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Email/get") get_response.method_responses in
-
let args = get_method.arguments in
-
match Ezjsonm.find_opt args ["list"] with
-
| Some (`A email_list) ->
-
let parse_results = List.map email_of_json email_list in
-
let (successes, failures) = List.partition Result.is_ok parse_results in
-
if List.length failures > 0 then
-
Lwt.return (Error (Parse_error "Failed to parse some emails"))
-
else
-
Lwt.return (Ok (List.map Result.get_ok successes))
-
| _ -> Lwt.return (Error (Parse_error "Email list not found in response"))
-
with
-
| Not_found -> Lwt.return (Error (Parse_error "Email/get method response not found"))
-
| e -> Lwt.return (Error (Parse_error (Printexc.to_string e))))
-
| Error e -> Lwt.return (Error e)
-
else
-
(* No emails in mailbox *)
-
Lwt.return (Ok [])
-
-
| _ -> Lwt.return (Error (Parse_error "Email IDs not found in query response"))
-
with
-
| Not_found -> Lwt.return (Error (Parse_error "Email/query method response not found"))
-
| Invalid_argument msg -> Lwt.return (Error (Parse_error msg))
-
| e -> Lwt.return (Error (Parse_error (Printexc.to_string e))))
-
| Error e -> Lwt.return (Error e)
-
-
(** Get a single email message by ID
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param email_id The email ID to retrieve
-
@return The email message if found
-
-
TODO:claude *)
-
let get_email conn ~account_id ~email_id =
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail
-
];
-
method_calls = [
-
{
-
name = "Email/get";
-
arguments = `O [
-
("accountId", `String account_id);
-
("ids", `A [`String email_id]);
-
];
-
method_call_id = "m1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* response_result = make_request conn.config request in
-
match response_result with
-
| Ok response ->
-
let result =
-
try
-
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Email/get") response.method_responses in
-
let args = method_response.arguments in
-
match Ezjsonm.find_opt args ["list"] with
-
| Some (`A [email]) -> email_of_json email
-
| Some (`A []) -> Error (Parse_error ("Email not found: " ^ email_id))
-
| _ -> Error (Parse_error "Expected single email in response")
-
with
-
| Not_found -> Error (Parse_error "Email/get method response not found")
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
Lwt.return result
-
| Error e -> Lwt.return (Error e)
-
-
(** Helper functions for working with message flags and mailbox attributes *)
-
-
(** Check if an email has a specific message keyword
-
@param email The email to check
-
@param keyword The message keyword to look for
-
@return true if the email has the keyword, false otherwise
-
-
TODO:claude *)
-
let has_message_keyword (email:Types.email) keyword =
-
let open Types in
-
let keyword_string = string_of_message_keyword keyword in
-
List.exists (function
-
| (Custom s, true) when s = keyword_string -> true
-
| _ -> false
-
) email.keywords
-
-
(** Add a message keyword to an email
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param email_id The email ID
-
@param keyword The message keyword to add
-
@return Success or error
-
-
TODO:claude *)
-
let add_message_keyword conn ~account_id ~email_id ~keyword =
-
let keyword_string = Types.string_of_message_keyword keyword in
-
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail
-
];
-
method_calls = [
-
{
-
name = "Email/set";
-
arguments = `O [
-
("accountId", `String account_id);
-
("update", `O [
-
(email_id, `O [
-
("keywords", `O [
-
(keyword_string, `Bool true)
-
])
-
])
-
]);
-
];
-
method_call_id = "m1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* response_result = make_request conn.config request in
-
match response_result with
-
| Ok response ->
-
let result =
-
try
-
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Email/set") response.method_responses in
-
let args = method_response.arguments in
-
match Ezjsonm.find_opt args ["updated"] with
-
| Some (`A _ids) -> Ok ()
-
| _ ->
-
match Ezjsonm.find_opt args ["notUpdated"] with
-
| Some (`O _errors) ->
-
Error (Parse_error ("Failed to update email: " ^ email_id))
-
| _ -> Error (Parse_error "Unexpected response format")
-
with
-
| Not_found -> Error (Parse_error "Email/set method response not found")
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
Lwt.return result
-
| Error e -> Lwt.return (Error e)
-
-
(** Set a flag color for an email
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param email_id The email ID
-
@param color The flag color to set
-
@return Success or error
-
-
TODO:claude *)
-
let set_flag_color conn ~account_id ~email_id ~color =
-
(* Get the bit pattern for the color *)
-
let (bit0, bit1, bit2) = Types.bits_of_flag_color color in
-
-
(* Build the keywords update object *)
-
let keywords = [
-
("$flagged", `Bool true);
-
("$MailFlagBit0", `Bool bit0);
-
("$MailFlagBit1", `Bool bit1);
-
("$MailFlagBit2", `Bool bit2);
-
] in
-
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail
-
];
-
method_calls = [
-
{
-
name = "Email/set";
-
arguments = `O [
-
("accountId", `String account_id);
-
("update", `O [
-
(email_id, `O [
-
("keywords", `O keywords)
-
])
-
]);
-
];
-
method_call_id = "m1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* response_result = make_request conn.config request in
-
match response_result with
-
| Ok response ->
-
let result =
-
try
-
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Email/set") response.method_responses in
-
let args = method_response.arguments in
-
match Ezjsonm.find_opt args ["updated"] with
-
| Some (`A _ids) -> Ok ()
-
| _ ->
-
match Ezjsonm.find_opt args ["notUpdated"] with
-
| Some (`O _errors) ->
-
Error (Parse_error ("Failed to update email: " ^ email_id))
-
| _ -> Error (Parse_error "Unexpected response format")
-
with
-
| Not_found -> Error (Parse_error "Email/set method response not found")
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
Lwt.return result
-
| Error e -> Lwt.return (Error e)
-
-
(** Convert an email's keywords to typed message_keyword list
-
@param email The email to analyze
-
@return List of message keywords
-
-
TODO:claude *)
-
let get_message_keywords (email:Types.email) =
-
let open Types in
-
List.filter_map (function
-
| (Custom s, true) -> Some (message_keyword_of_string s)
-
| _ -> None
-
) email.keywords
-
-
(** Get emails with a specific message keyword
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param keyword The message keyword to search for
-
@param limit Optional limit on number of emails to return
-
@return List of emails with the keyword if successful
-
-
TODO:claude *)
-
let get_emails_with_keyword conn ~account_id ~keyword ?limit () =
-
let keyword_string = Types.string_of_message_keyword keyword in
-
-
(* Query for emails with the specified keyword *)
-
let query_request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail
-
];
-
method_calls = [
-
{
-
name = "Email/query";
-
arguments = `O ([
-
("accountId", `String account_id);
-
("filter", `O [("hasKeyword", `String keyword_string)]);
-
("sort", `A [`O [("property", `String "receivedAt"); ("isAscending", `Bool false)]]);
-
] @ (match limit with
-
| Some l -> [("limit", `Float (float_of_int l))]
-
| None -> []
-
));
-
method_call_id = "q1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* query_result = make_request conn.config query_request in
-
match query_result with
-
| Ok query_response ->
-
(try
-
let query_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Email/query") query_response.method_responses in
-
let args = query_method.arguments in
-
match Ezjsonm.find_opt args ["ids"] with
-
| Some (`A ids) ->
-
let email_ids = List.map (function
-
| `String id -> id
-
| _ -> raise (Invalid_argument "Email ID is not a string")
-
) ids in
-
-
(* If we have IDs, fetch the actual email objects *)
-
if List.length email_ids > 0 then
-
let get_request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail
-
];
-
method_calls = [
-
{
-
name = "Email/get";
-
arguments = `O [
-
("accountId", `String account_id);
-
("ids", `A (List.map (fun id -> `String id) email_ids));
-
];
-
method_call_id = "g1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* get_result = make_request conn.config get_request in
-
match get_result with
-
| Ok get_response ->
-
(try
-
let get_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Email/get") get_response.method_responses in
-
let args = get_method.arguments in
-
match Ezjsonm.find_opt args ["list"] with
-
| Some (`A email_list) ->
-
let parse_results = List.map email_of_json email_list in
-
let (successes, failures) = List.partition Result.is_ok parse_results in
-
if List.length failures > 0 then
-
Lwt.return (Error (Parse_error "Failed to parse some emails"))
-
else
-
Lwt.return (Ok (List.map Result.get_ok successes))
-
| _ -> Lwt.return (Error (Parse_error "Email list not found in response"))
-
with
-
| Not_found -> Lwt.return (Error (Parse_error "Email/get method response not found"))
-
| e -> Lwt.return (Error (Parse_error (Printexc.to_string e))))
-
| Error e -> Lwt.return (Error e)
-
else
-
(* No emails with the keyword *)
-
Lwt.return (Ok [])
-
-
| _ -> Lwt.return (Error (Parse_error "Email IDs not found in query response"))
-
with
-
| Not_found -> Lwt.return (Error (Parse_error "Email/query method response not found"))
-
| Invalid_argument msg -> Lwt.return (Error (Parse_error msg))
-
| e -> Lwt.return (Error (Parse_error (Printexc.to_string e))))
-
| Error e -> Lwt.return (Error e)
-
-
(** {1 Email Submission} *)
-
-
(** Create a new email draft
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param mailbox_id The mailbox ID to store the draft in (usually "drafts")
-
@param from The sender's email address
-
@param to_addresses List of recipient email addresses
-
@param subject The email subject line
-
@param text_body Plain text message body
-
@param html_body Optional HTML message body
-
@return The created email ID if successful
-
-
TODO:claude
-
*)
-
let create_email_draft conn ~account_id ~mailbox_id ~from ~to_addresses ~subject ~text_body ?html_body () =
-
(* Create email addresses *)
-
let from_addr = {
-
Types.name = None;
-
email = from;
-
parameters = [];
-
} in
-
-
let to_addrs = List.map (fun addr -> {
-
Types.name = None;
-
email = addr;
-
parameters = [];
-
}) to_addresses in
-
-
(* Create text body part *)
-
let text_part = {
-
Types.part_id = Some "part1";
-
blob_id = None;
-
size = None;
-
headers = None;
-
name = None;
-
type_ = Some "text/plain";
-
charset = Some "utf-8";
-
disposition = None;
-
cid = None;
-
language = None;
-
location = None;
-
sub_parts = None;
-
header_parameter_name = None;
-
header_parameter_value = None;
-
} in
-
-
(* Create HTML body part if provided *)
-
let html_part_opt = match html_body with
-
| Some _html -> Some {
-
Types.part_id = Some "part2";
-
blob_id = None;
-
size = None;
-
headers = None;
-
name = None;
-
type_ = Some "text/html";
-
charset = Some "utf-8";
-
disposition = None;
-
cid = None;
-
language = None;
-
location = None;
-
sub_parts = None;
-
header_parameter_name = None;
-
header_parameter_value = None;
-
}
-
| None -> None
-
in
-
-
(* Create body values *)
-
let body_values = [
-
("part1", text_body)
-
] @ (match html_body with
-
| Some html -> [("part2", html)]
-
| None -> []
-
) in
-
-
(* Create email *)
-
let html_body_list = match html_part_opt with
-
| Some part -> Some [part]
-
| None -> None
-
in
-
-
let _email_creation = {
-
Types.mailbox_ids = [(mailbox_id, true)];
-
keywords = Some [(Draft, true)];
-
received_at = None; (* Server will set this *)
-
message_id = None; (* Server will generate this *)
-
in_reply_to = None;
-
references = None;
-
sender = None;
-
from = Some [from_addr];
-
to_ = Some to_addrs;
-
cc = None;
-
bcc = None;
-
reply_to = None;
-
subject = Some subject;
-
body_values = Some body_values;
-
text_body = Some [text_part];
-
html_body = html_body_list;
-
attachments = None;
-
headers = None;
-
} in
-
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail
-
];
-
method_calls = [
-
{
-
name = "Email/set";
-
arguments = `O [
-
("accountId", `String account_id);
-
("create", `O [
-
("draft1", `O (
-
[
-
("mailboxIds", `O [(mailbox_id, `Bool true)]);
-
("keywords", `O [("$draft", `Bool true)]);
-
("from", `A [`O [("name", `Null); ("email", `String from)]]);
-
("to", `A (List.map (fun addr ->
-
`O [("name", `Null); ("email", `String addr)]
-
) to_addresses));
-
("subject", `String subject);
-
("bodyStructure", `O [
-
("type", `String "multipart/alternative");
-
("subParts", `A [
-
`O [
-
("partId", `String "part1");
-
("type", `String "text/plain")
-
];
-
`O [
-
("partId", `String "part2");
-
("type", `String "text/html")
-
]
-
])
-
]);
-
("bodyValues", `O ([
-
("part1", `O [("value", `String text_body)])
-
] @ (match html_body with
-
| Some html -> [("part2", `O [("value", `String html)])]
-
| None -> [("part2", `O [("value", `String ("<html><body>" ^ text_body ^ "</body></html>"))])]
-
)))
-
]
-
))
-
])
-
];
-
method_call_id = "m1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* response_result = make_request conn.config request in
-
match response_result with
-
| Ok response ->
-
let result =
-
try
-
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Email/set") response.method_responses in
-
let args = method_response.arguments in
-
match Ezjsonm.find_opt args ["created"] with
-
| Some (`O created) ->
-
let draft_created = List.find_opt (fun (id, _) -> id = "draft1") created in
-
(match draft_created with
-
| Some (_, json) ->
-
let id = Ezjsonm.get_string (Ezjsonm.find json ["id"]) in
-
Ok id
-
| None -> Error (Parse_error "Created email not found in response"))
-
| _ ->
-
match Ezjsonm.find_opt args ["notCreated"] with
-
| Some (`O errors) ->
-
let error_msg = match List.find_opt (fun (id, _) -> id = "draft1") errors with
-
| Some (_, err) ->
-
let type_ = Ezjsonm.get_string (Ezjsonm.find err ["type"]) in
-
let description =
-
match Ezjsonm.find_opt err ["description"] with
-
| Some (`String desc) -> desc
-
| _ -> "Unknown error"
-
in
-
"Error type: " ^ type_ ^ ", Description: " ^ description
-
| None -> "Unknown error"
-
in
-
Error (Parse_error ("Failed to create email: " ^ error_msg))
-
| _ -> Error (Parse_error "Unexpected response format")
-
with
-
| Not_found -> Error (Parse_error "Email/set method response not found")
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
Lwt.return result
-
| Error e -> Lwt.return (Error e)
-
-
(** Get all identities for an account
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@return A list of identities if successful
-
-
TODO:claude
-
*)
-
let get_identities conn ~account_id =
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Submission
-
];
-
method_calls = [
-
{
-
name = "Identity/get";
-
arguments = `O [
-
("accountId", `String account_id);
-
];
-
method_call_id = "m1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* response_result = make_request conn.config request in
-
match response_result with
-
| Ok response ->
-
let result =
-
try
-
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Identity/get") response.method_responses in
-
let args = method_response.arguments in
-
match Ezjsonm.find_opt args ["list"] with
-
| Some (`A identities) ->
-
let parse_identity json =
-
try
-
let open Ezjsonm in
-
let id = get_string (find json ["id"]) in
-
let name = get_string (find json ["name"]) in
-
let email = get_string (find json ["email"]) in
-
-
let parse_email_addresses field =
-
match find_opt json [field] with
-
| Some (`A items) ->
-
Some (List.map (fun addr_json ->
-
let name =
-
match find_opt addr_json ["name"] with
-
| Some (`String s) -> Some s
-
| Some (`Null) -> None
-
| None -> None
-
| _ -> None
-
in
-
let email = get_string (find addr_json ["email"]) in
-
let parameters =
-
match find_opt addr_json ["parameters"] with
-
| Some (`O items) -> List.map (fun (k, v) ->
-
match v with
-
| `String s -> (k, s)
-
| _ -> (k, "")
-
) items
-
| _ -> []
-
in
-
{ Types.name; email; parameters }
-
) items)
-
| _ -> None
-
in
-
-
let reply_to = parse_email_addresses "replyTo" in
-
let bcc = parse_email_addresses "bcc" in
-
-
let text_signature =
-
match find_opt json ["textSignature"] with
-
| Some (`String s) -> Some s
-
| _ -> None
-
in
-
-
let html_signature =
-
match find_opt json ["htmlSignature"] with
-
| Some (`String s) -> Some s
-
| _ -> None
-
in
-
-
let may_delete =
-
match find_opt json ["mayDelete"] with
-
| Some (`Bool b) -> b
-
| _ -> false
-
in
-
-
(* Create our own identity record for simplicity *)
-
let r : Types.identity = {
-
id = id;
-
name = name;
-
email = email;
-
reply_to = reply_to;
-
bcc = bcc;
-
text_signature = text_signature;
-
html_signature = html_signature;
-
may_delete = may_delete
-
} in Ok r
-
with
-
| Not_found -> Error (Parse_error "Required field not found in identity object")
-
| Invalid_argument msg -> Error (Parse_error msg)
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
-
let results = List.map parse_identity identities in
-
let (successes, failures) = List.partition Result.is_ok results in
-
if List.length failures > 0 then
-
Error (Parse_error "Failed to parse some identity objects")
-
else
-
Ok (List.map Result.get_ok successes)
-
| _ -> Error (Parse_error "Identity list not found in response")
-
with
-
| Not_found -> Error (Parse_error "Identity/get method response not found")
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
Lwt.return result
-
| Error e -> Lwt.return (Error e)
-
-
(** Find a suitable identity by email address
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param email The email address to match
-
@return The identity if found, otherwise Error
-
-
TODO:claude
-
*)
-
let find_identity_by_email conn ~account_id ~email =
-
let* identities_result = get_identities conn ~account_id in
-
match identities_result with
-
| Ok identities -> begin
-
let matching_identity = List.find_opt (fun (identity:Types.identity) ->
-
(* Exact match *)
-
if String.lowercase_ascii identity.email = String.lowercase_ascii email then
-
true
-
else
-
(* Wildcard match (e.g., *@example.com) *)
-
let parts = String.split_on_char '@' identity.email in
-
if List.length parts = 2 && List.hd parts = "*" then
-
let domain = List.nth parts 1 in
-
let email_parts = String.split_on_char '@' email in
-
if List.length email_parts = 2 then
-
List.nth email_parts 1 = domain
-
else
-
false
-
else
-
false
-
) identities in
-
-
match matching_identity with
-
| Some identity -> Lwt.return (Ok identity)
-
| None -> Lwt.return (Error (Parse_error "No matching identity found"))
-
end
-
| Error e -> Lwt.return (Error e)
-
-
(** Submit an email for delivery
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param identity_id The identity ID to send from
-
@param email_id The email ID to submit
-
@param envelope Optional custom envelope
-
@return The submission ID if successful
-
-
TODO:claude
-
*)
-
let submit_email conn ~account_id ~identity_id ~email_id ?envelope () =
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail;
-
Capability.to_string Capability.Submission
-
];
-
method_calls = [
-
{
-
name = "EmailSubmission/set";
-
arguments = `O [
-
("accountId", `String account_id);
-
("create", `O [
-
("submission1", `O (
-
[
-
("emailId", `String email_id);
-
("identityId", `String identity_id);
-
] @ (match envelope with
-
| Some env -> [
-
("envelope", `O [
-
("mailFrom", `O [
-
("email", `String env.Types.mail_from.email);
-
("parameters", match env.Types.mail_from.parameters with
-
| Some params -> `O (List.map (fun (k, v) -> (k, `String v)) params)
-
| None -> `O []
-
)
-
]);
-
("rcptTo", `A (List.map (fun (rcpt:Types.submission_address) ->
-
`O [
-
("email", `String rcpt.Types.email);
-
("parameters", match rcpt.Types.parameters with
-
| Some params -> `O (List.map (fun (k, v) -> (k, `String v)) params)
-
| None -> `O []
-
)
-
]
-
) env.Types.rcpt_to))
-
])
-
]
-
| None -> []
-
)
-
))
-
]);
-
("onSuccessUpdateEmail", `O [
-
(email_id, `O [
-
("keywords", `O [
-
("$draft", `Bool false);
-
("$sent", `Bool true);
-
])
-
])
-
]);
-
];
-
method_call_id = "m1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* response_result = make_request conn.config request in
-
match response_result with
-
| Ok response ->
-
let result =
-
try
-
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "EmailSubmission/set") response.method_responses in
-
let args = method_response.arguments in
-
match Ezjsonm.find_opt args ["created"] with
-
| Some (`O created) ->
-
let submission_created = List.find_opt (fun (id, _) -> id = "submission1") created in
-
(match submission_created with
-
| Some (_, json) ->
-
let id = Ezjsonm.get_string (Ezjsonm.find json ["id"]) in
-
Ok id
-
| None -> Error (Parse_error "Created submission not found in response"))
-
| _ ->
-
match Ezjsonm.find_opt args ["notCreated"] with
-
| Some (`O errors) ->
-
let error_msg = match List.find_opt (fun (id, _) -> id = "submission1") errors with
-
| Some (_, err) ->
-
let type_ = Ezjsonm.get_string (Ezjsonm.find err ["type"]) in
-
let description =
-
match Ezjsonm.find_opt err ["description"] with
-
| Some (`String desc) -> desc
-
| _ -> "Unknown error"
-
in
-
"Error type: " ^ type_ ^ ", Description: " ^ description
-
| None -> "Unknown error"
-
in
-
Error (Parse_error ("Failed to submit email: " ^ error_msg))
-
| _ -> Error (Parse_error "Unexpected response format")
-
with
-
| Not_found -> Error (Parse_error "EmailSubmission/set method response not found")
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
Lwt.return result
-
| Error e -> Lwt.return (Error e)
-
-
(** Create and submit an email in one operation
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param from The sender's email address
-
@param to_addresses List of recipient email addresses
-
@param subject The email subject line
-
@param text_body Plain text message body
-
@param html_body Optional HTML message body
-
@return The submission ID if successful
-
-
TODO:claude
-
*)
-
let create_and_submit_email conn ~account_id ~from ~to_addresses ~subject ~text_body ?html_body:_ () =
-
(* First get accounts to find the draft mailbox and identity in a single request *)
-
let* initial_result =
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail;
-
Capability.to_string Capability.Submission
-
];
-
method_calls = [
-
{
-
name = "Mailbox/get";
-
arguments = `O [
-
("accountId", `String account_id);
-
];
-
method_call_id = "m1";
-
};
-
{
-
name = "Identity/get";
-
arguments = `O [
-
("accountId", `String account_id)
-
];
-
method_call_id = "m2";
-
}
-
];
-
created_ids = None;
-
} in
-
make_request conn.config request
-
in
-
-
match initial_result with
-
| Ok initial_response -> begin
-
(* Find drafts mailbox ID *)
-
let find_drafts_result =
-
try
-
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Mailbox/get") initial_response.method_responses in
-
let args = method_response.arguments in
-
match Ezjsonm.find_opt args ["list"] with
-
| Some (`A mailboxes) -> begin
-
let draft_mailbox = List.find_opt (fun mailbox ->
-
match Ezjsonm.find_opt mailbox ["role"] with
-
| Some (`String role) -> role = "drafts"
-
| _ -> false
-
) mailboxes in
-
-
match draft_mailbox with
-
| Some mb -> Ok (Ezjsonm.get_string (Ezjsonm.find mb ["id"]))
-
| None -> Error (Parse_error "No drafts mailbox found")
-
end
-
| _ -> Error (Parse_error "Mailbox list not found in response")
-
with
-
| Not_found -> Error (Parse_error "Mailbox/get method response not found")
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
-
(* Find matching identity for from address *)
-
let find_identity_result =
-
try
-
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "Identity/get") initial_response.method_responses in
-
let args = method_response.arguments in
-
match Ezjsonm.find_opt args ["list"] with
-
| Some (`A identities) -> begin
-
let matching_identity = List.find_opt (fun identity ->
-
match Ezjsonm.find_opt identity ["email"] with
-
| Some (`String email) ->
-
let email_lc = String.lowercase_ascii email in
-
let from_lc = String.lowercase_ascii from in
-
email_lc = from_lc || (* Exact match *)
-
(* Wildcard domain match *)
-
(let parts = String.split_on_char '@' email_lc in
-
if List.length parts = 2 && List.hd parts = "*" then
-
let domain = List.nth parts 1 in
-
let from_parts = String.split_on_char '@' from_lc in
-
if List.length from_parts = 2 then
-
List.nth from_parts 1 = domain
-
else false
-
else false)
-
| _ -> false
-
) identities in
-
-
match matching_identity with
-
| Some id ->
-
let identity_id = Ezjsonm.get_string (Ezjsonm.find id ["id"]) in
-
Ok identity_id
-
| None -> Error (Parse_error ("No matching identity found for " ^ from))
-
end
-
| _ -> Error (Parse_error "Identity list not found in response")
-
with
-
| Not_found -> Error (Parse_error "Identity/get method response not found")
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
-
(* If we have both required IDs, create and submit the email in one request *)
-
match (find_drafts_result, find_identity_result) with
-
| (Ok drafts_id, Ok identity_id) -> begin
-
(* Now create and submit the email in a single request *)
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Mail;
-
Capability.to_string Capability.Submission
-
];
-
method_calls = [
-
{
-
name = "Email/set";
-
arguments = `O [
-
("accountId", `String account_id);
-
("create", `O [
-
("draft", `O (
-
[
-
("mailboxIds", `O [(drafts_id, `Bool true)]);
-
("keywords", `O [("$draft", `Bool true)]);
-
("from", `A [`O [("email", `String from)]]);
-
("to", `A (List.map (fun addr ->
-
`O [("email", `String addr)]
-
) to_addresses));
-
("subject", `String subject);
-
("textBody", `A [`O [
-
("partId", `String "body");
-
("type", `String "text/plain")
-
]]);
-
("bodyValues", `O [
-
("body", `O [
-
("charset", `String "utf-8");
-
("value", `String text_body)
-
])
-
])
-
]
-
))
-
]);
-
];
-
method_call_id = "0";
-
};
-
{
-
name = "EmailSubmission/set";
-
arguments = `O [
-
("accountId", `String account_id);
-
("create", `O [
-
("sendIt", `O [
-
("emailId", `String "#draft");
-
("identityId", `String identity_id)
-
])
-
])
-
];
-
method_call_id = "1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* submit_result = make_request conn.config request in
-
match submit_result with
-
| Ok submit_response -> begin
-
try
-
let submission_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "EmailSubmission/set") submit_response.method_responses in
-
let args = submission_method.arguments in
-
-
(* Check if email was created and submission was created *)
-
match Ezjsonm.find_opt args ["created"] with
-
| Some (`O created) -> begin
-
(* Extract the submission ID *)
-
let submission_created = List.find_opt (fun (id, _) -> id = "sendIt") created in
-
match submission_created with
-
| Some (_, json) ->
-
let id = Ezjsonm.get_string (Ezjsonm.find json ["id"]) in
-
Lwt.return (Ok id)
-
| None -> begin
-
(* Check if there was an error in creation *)
-
match Ezjsonm.find_opt args ["notCreated"] with
-
| Some (`O errors) ->
-
let error_msg = match List.find_opt (fun (id, _) -> id = "sendIt") errors with
-
| Some (_, err) ->
-
let type_ = Ezjsonm.get_string (Ezjsonm.find err ["type"]) in
-
let description =
-
match Ezjsonm.find_opt err ["description"] with
-
| Some (`String desc) -> desc
-
| _ -> "Unknown error"
-
in
-
"Error type: " ^ type_ ^ ", Description: " ^ description
-
| None -> "Unknown error"
-
in
-
Lwt.return (Error (Parse_error ("Failed to submit email: " ^ error_msg)))
-
| Some _ -> Lwt.return (Error (Parse_error "Email submission not found in response"))
-
| None -> Lwt.return (Error (Parse_error "Email submission not found in response"))
-
end
-
end
-
| Some (`Null) -> Lwt.return (Error (Parse_error "No created submissions in response"))
-
| Some _ -> Lwt.return (Error (Parse_error "Invalid response format for created submissions"))
-
| None -> Lwt.return (Error (Parse_error "No created submissions in response"))
-
with
-
| Not_found -> Lwt.return (Error (Parse_error "EmailSubmission/set method response not found"))
-
| e -> Lwt.return (Error (Parse_error (Printexc.to_string e)))
-
end
-
| Error e -> Lwt.return (Error e)
-
end
-
| (Error e, _) -> Lwt.return (Error e)
-
| (_, Error e) -> Lwt.return (Error e)
-
end
-
| Error e -> Lwt.return (Error e)
-
-
(** Get status of an email submission
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param submission_id The email submission ID
-
@return The submission status if successful
-
-
TODO:claude
-
*)
-
let get_submission_status conn ~account_id ~submission_id =
-
let request = {
-
using = [
-
Jmap.Capability.to_string Jmap.Capability.Core;
-
Capability.to_string Capability.Submission
-
];
-
method_calls = [
-
{
-
name = "EmailSubmission/get";
-
arguments = `O [
-
("accountId", `String account_id);
-
("ids", `A [`String submission_id]);
-
];
-
method_call_id = "m1";
-
}
-
];
-
created_ids = None;
-
} in
-
-
let* response_result = make_request conn.config request in
-
match response_result with
-
| Ok response ->
-
let result =
-
try
-
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
-
inv.name = "EmailSubmission/get") response.method_responses in
-
let args = method_response.arguments in
-
match Ezjsonm.find_opt args ["list"] with
-
| Some (`A [submission]) ->
-
let parse_submission json =
-
try
-
let open Ezjsonm in
-
let id = get_string (find json ["id"]) in
-
let identity_id = get_string (find json ["identityId"]) in
-
let email_id = get_string (find json ["emailId"]) in
-
let thread_id = get_string (find json ["threadId"]) in
-
-
let envelope =
-
match find_opt json ["envelope"] with
-
| Some (`O env) -> begin
-
let parse_address addr_json =
-
let email = get_string (find addr_json ["email"]) in
-
let parameters =
-
match find_opt addr_json ["parameters"] with
-
| Some (`O params) ->
-
Some (List.map (fun (k, v) -> (k, get_string v)) params)
-
| _ -> None
-
in
-
{ Types.email; parameters }
-
in
-
-
let mail_from = parse_address (find (`O env) ["mailFrom"]) in
-
let rcpt_to =
-
match find (`O env) ["rcptTo"] with
-
| `A rcpts -> List.map parse_address rcpts
-
| _ -> []
-
in
-
-
Some { Types.mail_from; rcpt_to }
-
end
-
| _ -> None
-
in
-
-
let send_at =
-
match find_opt json ["sendAt"] with
-
| Some (`String date) -> Some date
-
| _ -> None
-
in
-
-
let undo_status =
-
match find_opt json ["undoStatus"] with
-
| Some (`String "pending") -> Some `pending
-
| Some (`String "final") -> Some `final
-
| Some (`String "canceled") -> Some `canceled
-
| _ -> None
-
in
-
-
let parse_delivery_status deliveries =
-
match deliveries with
-
| `O statuses ->
-
Some (List.map (fun (email, status_json) ->
-
let smtp_reply = get_string (find status_json ["smtpReply"]) in
-
let delivered =
-
match find_opt status_json ["delivered"] with
-
| Some (`String d) -> Some d
-
| _ -> None
-
in
-
(email, { Types.smtp_reply; delivered })
-
) statuses)
-
| _ -> None
-
in
-
-
let delivery_status =
-
match find_opt json ["deliveryStatus"] with
-
| Some status -> parse_delivery_status status
-
| _ -> None
-
in
-
-
let dsn_blob_ids =
-
match find_opt json ["dsnBlobIds"] with
-
| Some (`O ids) -> Some (List.map (fun (email, id) -> (email, get_string id)) ids)
-
| _ -> None
-
in
-
-
let mdn_blob_ids =
-
match find_opt json ["mdnBlobIds"] with
-
| Some (`O ids) -> Some (List.map (fun (email, id) -> (email, get_string id)) ids)
-
| _ -> None
-
in
-
-
Ok {
-
Types.id;
-
identity_id;
-
email_id;
-
thread_id;
-
envelope;
-
send_at;
-
undo_status;
-
delivery_status;
-
dsn_blob_ids;
-
mdn_blob_ids;
-
}
-
with
-
| Not_found -> Error (Parse_error "Required field not found in submission object")
-
| Invalid_argument msg -> Error (Parse_error msg)
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
-
parse_submission submission
-
| Some (`A []) -> Error (Parse_error ("Submission not found: " ^ submission_id))
-
| _ -> Error (Parse_error "Expected single submission in response")
-
with
-
| Not_found -> Error (Parse_error "EmailSubmission/get method response not found")
-
| e -> Error (Parse_error (Printexc.to_string e))
-
in
-
Lwt.return result
-
| Error e -> Lwt.return (Error e)
-
-
(** {1 Email Address Utilities} *)
-
-
(** Custom implementation of substring matching *)
-
let contains_substring str sub =
-
try
-
let _ = Str.search_forward (Str.regexp_string sub) str 0 in
-
true
-
with Not_found -> false
-
-
(** Checks if a pattern with wildcards matches a string
-
@param pattern Pattern string with * and ? wildcards
-
@param str String to match against
-
Based on simple recursive wildcard matching algorithm
-
*)
-
let matches_wildcard pattern str =
-
let pattern_len = String.length pattern in
-
let str_len = String.length str in
-
-
(* Convert both to lowercase for case-insensitive matching *)
-
let pattern = String.lowercase_ascii pattern in
-
let str = String.lowercase_ascii str in
-
-
(* If there are no wildcards, do a simple substring check *)
-
if not (String.contains pattern '*' || String.contains pattern '?') then
-
contains_substring str pattern
-
else
-
(* Classic recursive matching algorithm *)
-
let rec match_from p_pos s_pos =
-
(* Pattern matched to the end *)
-
if p_pos = pattern_len then
-
s_pos = str_len
-
(* Star matches zero or more chars *)
-
else if pattern.[p_pos] = '*' then
-
match_from (p_pos + 1) s_pos || (* Match empty string *)
-
(s_pos < str_len && match_from p_pos (s_pos + 1)) (* Match one more char *)
-
(* If both have more chars and they match or ? wildcard *)
-
else if s_pos < str_len &&
-
(pattern.[p_pos] = '?' || pattern.[p_pos] = str.[s_pos]) then
-
match_from (p_pos + 1) (s_pos + 1)
-
else
-
false
-
in
-
-
match_from 0 0
-
-
(** Check if an email address matches a filter string
-
@param email The email address to check
-
@param pattern The filter pattern to match against
-
@return True if the email address matches the filter
-
*)
-
let email_address_matches email pattern =
-
matches_wildcard pattern email
-
-
(** Check if an email matches a sender filter
-
@param email The email object to check
-
@param pattern The sender filter pattern
-
@return True if any sender address matches the filter
-
*)
-
let email_matches_sender (email : Types.email) pattern =
-
(* Helper to extract emails from address list *)
-
let addresses_match addrs =
-
List.exists (fun (addr : Types.email_address) ->
-
email_address_matches addr.email pattern
-
) addrs
-
in
-
-
(* Check From addresses first *)
-
let from_match =
-
match email.Types.from with
-
| Some addrs -> addresses_match addrs
-
| None -> false
-
in
-
-
(* If no match in From, check Sender field *)
-
if from_match then true
-
else
-
match email.Types.sender with
-
| Some addrs -> addresses_match addrs
-
| None -> false
···
-1655
lib/jmap_mail.mli
···
-
(** Implementation of the JMAP Mail extension, as defined in RFC8621
-
@see <https://datatracker.ietf.org/doc/html/rfc8621> RFC8621
-
-
This module implements the JMAP Mail specification, providing types and
-
functions for working with emails, mailboxes, threads, and other mail-related
-
objects in the JMAP protocol.
-
*)
-
-
(** Module for managing JMAP Mail-specific capability URIs as defined in RFC8621 Section 1.3
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-1.3> RFC8621 Section 1.3
-
*)
-
module Capability : sig
-
(** Mail capability URI as defined in RFC8621 Section 1.3
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-1.3>
-
*)
-
val mail_uri : string
-
-
(** Submission capability URI as defined in RFC8621 Section 1.3
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-1.3>
-
*)
-
val submission_uri : string
-
-
(** Vacation response capability URI as defined in RFC8621 Section 1.3
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-1.3>
-
*)
-
val vacation_response_uri : string
-
-
(** All mail extension capability types as defined in RFC8621 Section 1.3
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-1.3>
-
*)
-
type t =
-
| Mail (** Mail capability for emails and mailboxes *)
-
| Submission (** Submission capability for sending emails *)
-
| VacationResponse (** Vacation response capability for auto-replies *)
-
| Extension of string (** Custom extension capabilities *)
-
-
(** Convert capability to URI string
-
@param capability The capability to convert
-
@return The full URI string for the capability
-
*)
-
val to_string : t -> string
-
-
(** Parse a string to a capability
-
@param uri The capability URI string to parse
-
@return The parsed capability type
-
*)
-
val of_string : string -> t
-
-
(** Check if a capability is a standard mail capability
-
@param capability The capability to check
-
@return True if the capability is a standard JMAP Mail capability
-
*)
-
val is_standard : t -> bool
-
-
(** Check if a capability string is a standard mail capability
-
@param uri The capability URI string to check
-
@return True if the string represents a standard JMAP Mail capability
-
*)
-
val is_standard_string : string -> bool
-
-
(** Create a list of capability strings
-
@param capabilities List of capability types
-
@return List of capability URI strings
-
*)
-
val strings_of_capabilities : t list -> string list
-
end
-
-
(** Types for the JMAP Mail extension as defined in RFC8621
-
@see <https://datatracker.ietf.org/doc/html/rfc8621>
-
*)
-
module Types : sig
-
open Jmap.Types
-
-
(** {1 Mail capabilities}
-
Capability URIs for JMAP Mail extension as defined in RFC8621 Section 1.3
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-1.3>
-
*)
-
-
(** Capability URI for JMAP Mail as defined in RFC8621 Section 1.3
-
Identifies support for the Mail data model
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-1.3>
-
*)
-
val capability_mail : string
-
-
(** Capability URI for JMAP Submission as defined in RFC8621 Section 1.3
-
Identifies support for email submission
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-1.3>
-
*)
-
val capability_submission : string
-
-
(** Capability URI for JMAP Vacation Response as defined in RFC8621 Section 1.3
-
Identifies support for vacation auto-reply functionality
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-1.3>
-
*)
-
val capability_vacation_response : string
-
-
(** {1:mailbox Mailbox objects}
-
Mailbox types as defined in RFC8621 Section 2
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2>
-
*)
-
-
(** A role for a mailbox as defined in RFC8621 Section 2.
-
Standardized roles for special mailboxes like Inbox, Sent, etc.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2>
-
*)
-
type mailbox_role =
-
| All (** All mail mailbox *)
-
| Archive (** Archived mail mailbox *)
-
| Drafts (** Draft messages mailbox *)
-
| Flagged (** Starred/flagged mail mailbox *)
-
| Important (** Important mail mailbox *)
-
| Inbox (** Primary inbox mailbox *)
-
| Junk (** Spam/Junk mail mailbox *)
-
| Sent (** Sent mail mailbox *)
-
| Trash (** Deleted/Trash mail mailbox *)
-
| Unknown of string (** Server-specific custom roles *)
-
-
(** A mailbox (folder) in a mail account as defined in RFC8621 Section 2.
-
Represents an email folder or label in the account.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2>
-
*)
-
type mailbox = {
-
id : id; (** Server-assigned ID for the mailbox *)
-
name : string; (** User-visible name for the mailbox *)
-
parent_id : id option; (** ID of the parent mailbox, if any *)
-
role : mailbox_role option; (** The role of this mailbox, if it's a special mailbox *)
-
sort_order : unsigned_int; (** Position for mailbox in the UI *)
-
total_emails : unsigned_int; (** Total number of emails in the mailbox *)
-
unread_emails : unsigned_int; (** Number of unread emails in the mailbox *)
-
total_threads : unsigned_int; (** Total number of threads in the mailbox *)
-
unread_threads : unsigned_int; (** Number of threads with unread emails *)
-
is_subscribed : bool; (** Has the user subscribed to this mailbox *)
-
my_rights : mailbox_rights; (** Access rights for the user on this mailbox *)
-
}
-
-
(** Rights for a mailbox as defined in RFC8621 Section 2.
-
Determines the operations a user can perform on a mailbox.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2>
-
*)
-
and mailbox_rights = {
-
may_read_items : bool; (** Can the user read messages in this mailbox *)
-
may_add_items : bool; (** Can the user add messages to this mailbox *)
-
may_remove_items : bool; (** Can the user remove messages from this mailbox *)
-
may_set_seen : bool; (** Can the user mark messages as read/unread *)
-
may_set_keywords : bool; (** Can the user set keywords/flags on messages *)
-
may_create_child : bool; (** Can the user create child mailboxes *)
-
may_rename : bool; (** Can the user rename this mailbox *)
-
may_delete : bool; (** Can the user delete this mailbox *)
-
may_submit : bool; (** Can the user submit messages in this mailbox for delivery *)
-
}
-
-
(** Filter condition for mailbox queries as defined in RFC8621 Section 2.3.
-
Used to filter mailboxes in queries.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.3>
-
*)
-
type mailbox_filter_condition = {
-
parent_id : id option; (** Only include mailboxes with this parent *)
-
name : string option; (** Only include mailboxes with this name (case-insensitive substring match) *)
-
role : string option; (** Only include mailboxes with this role *)
-
has_any_role : bool option; (** If true, only include mailboxes with a role, if false those without *)
-
is_subscribed : bool option; (** If true, only include subscribed mailboxes, if false unsubscribed *)
-
}
-
-
(** Filter for mailbox queries as defined in RFC8621 Section 2.3.
-
Complex filter for Mailbox/query method.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.3>
-
*)
-
type mailbox_query_filter = [
-
| `And of mailbox_query_filter list (** Logical AND of filters *)
-
| `Or of mailbox_query_filter list (** Logical OR of filters *)
-
| `Not of mailbox_query_filter (** Logical NOT of a filter *)
-
| `Condition of mailbox_filter_condition (** Simple condition filter *)
-
]
-
-
(** Mailbox/get request arguments as defined in RFC8621 Section 2.1.
-
Used to fetch mailboxes by ID.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.1>
-
*)
-
type mailbox_get_arguments = {
-
account_id : id; (** The account to fetch mailboxes from *)
-
ids : id list option; (** The IDs of mailboxes to fetch, null means all *)
-
properties : string list option; (** Properties to return, null means all *)
-
}
-
-
(** Mailbox/get response as defined in RFC8621 Section 2.1.
-
Contains requested mailboxes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.1>
-
*)
-
type mailbox_get_response = {
-
account_id : id; (** The account from which mailboxes were fetched *)
-
state : string; (** A string representing the state on the server *)
-
list : mailbox list; (** The list of mailboxes requested *)
-
not_found : id list; (** IDs requested that could not be found *)
-
}
-
-
(** Mailbox/changes request arguments as defined in RFC8621 Section 2.2.
-
Used to get mailbox changes since a previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.2>
-
*)
-
type mailbox_changes_arguments = {
-
account_id : id; (** The account to get changes for *)
-
since_state : string; (** The previous state to compare to *)
-
max_changes : unsigned_int option; (** Maximum number of changes to return *)
-
}
-
-
(** Mailbox/changes response as defined in RFC8621 Section 2.2.
-
Reports mailboxes that have changed since a previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.2>
-
*)
-
type mailbox_changes_response = {
-
account_id : id; (** The account changes are for *)
-
old_state : string; (** The state provided in the request *)
-
new_state : string; (** The current state on the server *)
-
has_more_changes : bool; (** If true, more changes are available *)
-
created : id list; (** IDs of mailboxes created since old_state *)
-
updated : id list; (** IDs of mailboxes updated since old_state *)
-
destroyed : id list; (** IDs of mailboxes destroyed since old_state *)
-
}
-
-
(** Mailbox/query request arguments as defined in RFC8621 Section 2.3.
-
Used to query mailboxes based on filter criteria.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.3>
-
*)
-
type mailbox_query_arguments = {
-
account_id : id; (** The account to query *)
-
filter : mailbox_query_filter option; (** Filter to match mailboxes against *)
-
sort : [ `name | `role | `sort_order ] list option; (** Sort criteria *)
-
limit : unsigned_int option; (** Maximum number of results to return *)
-
}
-
-
(** Mailbox/query response as defined in RFC8621 Section 2.3.
-
Contains IDs of mailboxes matching the query.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.3>
-
*)
-
type mailbox_query_response = {
-
account_id : id; (** The account that was queried *)
-
query_state : string; (** State string for the query results *)
-
can_calculate_changes : bool; (** Whether queryChanges can be used with these results *)
-
position : unsigned_int; (** Zero-based index of the first result *)
-
ids : id list; (** IDs of mailboxes matching the query *)
-
total : unsigned_int option; (** Total number of matches if requested *)
-
}
-
-
(** Mailbox/queryChanges request arguments as defined in RFC8621 Section 2.4.
-
Used to get changes to mailbox query results.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.4>
-
*)
-
type mailbox_query_changes_arguments = {
-
account_id : id; (** The account to query *)
-
filter : mailbox_query_filter option; (** Same filter as the original query *)
-
sort : [ `name | `role | `sort_order ] list option; (** Same sort as the original query *)
-
since_query_state : string; (** The query_state from the previous result *)
-
max_changes : unsigned_int option; (** Maximum number of changes to return *)
-
up_to_id : id option; (** ID of the last mailbox to check for changes *)
-
}
-
-
(** Mailbox/queryChanges response as defined in RFC8621 Section 2.4.
-
Reports changes to a mailbox query since the previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.4>
-
*)
-
type mailbox_query_changes_response = {
-
account_id : id; (** The account that was queried *)
-
old_query_state : string; (** The query_state from the request *)
-
new_query_state : string; (** The current query_state on the server *)
-
total : unsigned_int option; (** Updated total number of matches, if requested *)
-
removed : id list; (** IDs that were in the old results but not the new *)
-
added : mailbox_query_changes_added list; (** IDs that are in the new results but not the old *)
-
}
-
-
(** Added item in mailbox query changes as defined in RFC8621 Section 2.4.
-
Represents a mailbox added to query results.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.4>
-
*)
-
and mailbox_query_changes_added = {
-
id : id; (** ID of the added mailbox *)
-
index : unsigned_int; (** Zero-based index of the added mailbox in the results *)
-
}
-
-
(** Mailbox/set request arguments as defined in RFC8621 Section 2.5.
-
Used to create, update, and destroy mailboxes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.5>
-
*)
-
type mailbox_set_arguments = {
-
account_id : id; (** The account to make changes in *)
-
if_in_state : string option; (** Only apply changes if in this state *)
-
create : (id * mailbox_creation) list option; (** Map of creation IDs to mailboxes to create *)
-
update : (id * mailbox_update) list option; (** Map of IDs to update properties *)
-
destroy : id list option; (** List of IDs to destroy *)
-
}
-
-
(** Properties for mailbox creation as defined in RFC8621 Section 2.5.
-
Used to create new mailboxes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.5>
-
*)
-
and mailbox_creation = {
-
name : string; (** Name for the new mailbox *)
-
parent_id : id option; (** ID of the parent mailbox, if any *)
-
role : string option; (** Role for the mailbox, if it's a special-purpose mailbox *)
-
sort_order : unsigned_int option; (** Sort order, defaults to 0 *)
-
is_subscribed : bool option; (** Whether the mailbox is subscribed, defaults to true *)
-
}
-
-
(** Properties for mailbox update as defined in RFC8621 Section 2.5.
-
Used to update existing mailboxes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.5>
-
*)
-
and mailbox_update = {
-
name : string option; (** New name for the mailbox *)
-
parent_id : id option; (** New parent ID for the mailbox *)
-
role : string option; (** New role for the mailbox *)
-
sort_order : unsigned_int option; (** New sort order for the mailbox *)
-
is_subscribed : bool option; (** New subscription status for the mailbox *)
-
}
-
-
(** Mailbox/set response as defined in RFC8621 Section 2.5.
-
Reports the results of mailbox changes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-2.5>
-
*)
-
type mailbox_set_response = {
-
account_id : id; (** The account that was modified *)
-
old_state : string option; (** The state before processing, if changed *)
-
new_state : string; (** The current state on the server *)
-
created : (id * mailbox) list option; (** Map of creation IDs to created mailboxes *)
-
updated : id list option; (** List of IDs that were successfully updated *)
-
destroyed : id list option; (** List of IDs that were successfully destroyed *)
-
not_created : (id * set_error) list option; (** Map of IDs to errors for failed creates *)
-
not_updated : (id * set_error) list option; (** Map of IDs to errors for failed updates *)
-
not_destroyed : (id * set_error) list option; (** Map of IDs to errors for failed destroys *)
-
}
-
-
(** {1:thread Thread objects}
-
Thread types as defined in RFC8621 Section 3
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-3>
-
*)
-
-
(** A thread in a mail account as defined in RFC8621 Section 3.
-
Represents a group of related email messages.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-3>
-
*)
-
type thread = {
-
id : id; (** Server-assigned ID for the thread *)
-
email_ids : id list; (** IDs of emails in the thread *)
-
}
-
-
(** Thread/get request arguments as defined in RFC8621 Section 3.1.
-
Used to fetch threads by ID.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-3.1>
-
*)
-
type thread_get_arguments = {
-
account_id : id; (** The account to fetch threads from *)
-
ids : id list option; (** The IDs of threads to fetch, null means all *)
-
properties : string list option; (** Properties to return, null means all *)
-
}
-
-
(** Thread/get response as defined in RFC8621 Section 3.1.
-
Contains requested threads.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-3.1>
-
*)
-
type thread_get_response = {
-
account_id : id; (** The account from which threads were fetched *)
-
state : string; (** A string representing the state on the server *)
-
list : thread list; (** The list of threads requested *)
-
not_found : id list; (** IDs requested that could not be found *)
-
}
-
-
(** Thread/changes request arguments as defined in RFC8621 Section 3.2.
-
Used to get thread changes since a previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-3.2>
-
*)
-
type thread_changes_arguments = {
-
account_id : id; (** The account to get changes for *)
-
since_state : string; (** The previous state to compare to *)
-
max_changes : unsigned_int option; (** Maximum number of changes to return *)
-
}
-
-
(** Thread/changes response as defined in RFC8621 Section 3.2.
-
Reports threads that have changed since a previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-3.2>
-
*)
-
type thread_changes_response = {
-
account_id : id; (** The account changes are for *)
-
old_state : string; (** The state provided in the request *)
-
new_state : string; (** The current state on the server *)
-
has_more_changes : bool; (** If true, more changes are available *)
-
created : id list; (** IDs of threads created since old_state *)
-
updated : id list; (** IDs of threads updated since old_state *)
-
destroyed : id list; (** IDs of threads destroyed since old_state *)
-
}
-
-
(** {1:email Email objects}
-
Email types as defined in RFC8621 Section 4
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4>
-
*)
-
-
(** Addressing (mailbox) information as defined in RFC8621 Section 4.1.1.
-
Represents an email address with optional display name.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.1.1>
-
*)
-
type email_address = {
-
name : string option; (** Display name of the mailbox (e.g., "John Doe") *)
-
email : string; (** The email address (e.g., "john@example.com") *)
-
parameters : (string * string) list; (** Additional parameters for the address *)
-
}
-
-
(** Message header field as defined in RFC8621 Section 4.1.2.
-
Represents an email header.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.1.2>
-
*)
-
type header = {
-
name : string; (** Name of the header field (e.g., "Subject") *)
-
value : string; (** Value of the header field *)
-
}
-
-
(** Email keyword (flag) as defined in RFC8621 Section 4.3.
-
Represents a flag or tag on an email message.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.3>
-
*)
-
type keyword =
-
| Flagged (** Message is flagged/starred *)
-
| Answered (** Message has been replied to *)
-
| Draft (** Message is a draft *)
-
| Forwarded (** Message has been forwarded *)
-
| Phishing (** Message has been reported as phishing *)
-
| Junk (** Message is spam/junk *)
-
| NotJunk (** Message is explicitly not spam *)
-
| Seen (** Message has been read *)
-
| Unread (** Message is unread (inverse of $seen) *)
-
| Custom of string (** Custom/non-standard keywords *)
-
-
(** Email message as defined in RFC8621 Section 4.
-
Represents an email message in a mail account.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4>
-
*)
-
type email = {
-
id : id; (** Server-assigned ID for the message *)
-
blob_id : id; (** ID of the raw message content blob *)
-
thread_id : id; (** ID of the thread this message belongs to *)
-
mailbox_ids : (id * bool) list; (** Map of mailbox IDs to boolean (whether message belongs to mailbox) *)
-
keywords : (keyword * bool) list; (** Map of keywords to boolean (whether message has keyword) *)
-
size : unsigned_int; (** Size of the message in octets *)
-
received_at : utc_date; (** When the message was received by the server *)
-
message_id : string list; (** Message-ID header values *)
-
in_reply_to : string list option; (** In-Reply-To header values *)
-
references : string list option; (** References header values *)
-
sender : email_address list option; (** Sender header addresses *)
-
from : email_address list option; (** From header addresses *)
-
to_ : email_address list option; (** To header addresses *)
-
cc : email_address list option; (** Cc header addresses *)
-
bcc : email_address list option; (** Bcc header addresses *)
-
reply_to : email_address list option; (** Reply-To header addresses *)
-
subject : string option; (** Subject header value *)
-
sent_at : utc_date option; (** Date header value as a date-time *)
-
has_attachment : bool option; (** Does the message have any attachments *)
-
preview : string option; (** Preview of the message (first bit of text) *)
-
body_values : (string * string) list option; (** Map of part IDs to text content *)
-
text_body : email_body_part list option; (** Plain text message body parts *)
-
html_body : email_body_part list option; (** HTML message body parts *)
-
attachments : email_body_part list option; (** Attachment parts in the message *)
-
headers : header list option; (** All headers in the message *)
-
}
-
-
(** Email body part as defined in RFC8621 Section 4.1.4.
-
Represents a MIME part in an email message.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.1.4>
-
*)
-
and email_body_part = {
-
part_id : string option; (** Server-assigned ID for the MIME part *)
-
blob_id : id option; (** ID of the raw content for this part *)
-
size : unsigned_int option; (** Size of the part in octets *)
-
headers : header list option; (** Headers for this MIME part *)
-
name : string option; (** Filename of this part, if any *)
-
type_ : string option; (** MIME type of the part *)
-
charset : string option; (** Character set of the part, if applicable *)
-
disposition : string option; (** Content-Disposition value *)
-
cid : string option; (** Content-ID value *)
-
language : string list option; (** Content-Language values *)
-
location : string option; (** Content-Location value *)
-
sub_parts : email_body_part list option; (** Child MIME parts for multipart types *)
-
header_parameter_name : string option; (** Header parameter name (for headers with parameters) *)
-
header_parameter_value : string option; (** Header parameter value (for headers with parameters) *)
-
}
-
-
(** Email query filter condition as defined in RFC8621 Section 4.4.
-
Specifies conditions for filtering emails in queries.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.4>
-
*)
-
type email_filter_condition = {
-
in_mailbox : id option; (** Only include emails in this mailbox *)
-
in_mailbox_other_than : id list option; (** Only include emails not in these mailboxes *)
-
min_size : unsigned_int option; (** Only include emails of at least this size in octets *)
-
max_size : unsigned_int option; (** Only include emails of at most this size in octets *)
-
before : utc_date option; (** Only include emails received before this date-time *)
-
after : utc_date option; (** Only include emails received after this date-time *)
-
header : (string * string) option; (** Only include emails with header matching value (name, value) *)
-
from : string option; (** Only include emails with From containing this text *)
-
to_ : string option; (** Only include emails with To containing this text *)
-
cc : string option; (** Only include emails with CC containing this text *)
-
bcc : string option; (** Only include emails with BCC containing this text *)
-
subject : string option; (** Only include emails with Subject containing this text *)
-
body : string option; (** Only include emails with body containing this text *)
-
has_keyword : string option; (** Only include emails with this keyword *)
-
not_keyword : string option; (** Only include emails without this keyword *)
-
has_attachment : bool option; (** If true, only include emails with attachments *)
-
text : string option; (** Only include emails with this text in headers or body *)
-
}
-
-
(** Filter for email queries as defined in RFC8621 Section 4.4.
-
Complex filter for Email/query method.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.4>
-
*)
-
type email_query_filter = [
-
| `And of email_query_filter list (** Logical AND of filters *)
-
| `Or of email_query_filter list (** Logical OR of filters *)
-
| `Not of email_query_filter (** Logical NOT of a filter *)
-
| `Condition of email_filter_condition (** Simple condition filter *)
-
]
-
-
(** Email/get request arguments as defined in RFC8621 Section 4.5.
-
Used to fetch emails by ID.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.5>
-
*)
-
type email_get_arguments = {
-
account_id : id; (** The account to fetch emails from *)
-
ids : id list option; (** The IDs of emails to fetch, null means all *)
-
properties : string list option; (** Properties to return, null means all *)
-
body_properties : string list option; (** Properties to return on body parts *)
-
fetch_text_body_values : bool option; (** Whether to fetch text body content *)
-
fetch_html_body_values : bool option; (** Whether to fetch HTML body content *)
-
fetch_all_body_values : bool option; (** Whether to fetch all body content *)
-
max_body_value_bytes : unsigned_int option; (** Maximum size of body values to return *)
-
}
-
-
(** Email/get response as defined in RFC8621 Section 4.5.
-
Contains requested emails.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.5>
-
*)
-
type email_get_response = {
-
account_id : id; (** The account from which emails were fetched *)
-
state : string; (** A string representing the state on the server *)
-
list : email list; (** The list of emails requested *)
-
not_found : id list; (** IDs requested that could not be found *)
-
}
-
-
(** Email/changes request arguments as defined in RFC8621 Section 4.6.
-
Used to get email changes since a previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.6>
-
*)
-
type email_changes_arguments = {
-
account_id : id; (** The account to get changes for *)
-
since_state : string; (** The previous state to compare to *)
-
max_changes : unsigned_int option; (** Maximum number of changes to return *)
-
}
-
-
(** Email/changes response as defined in RFC8621 Section 4.6.
-
Reports emails that have changed since a previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.6>
-
*)
-
type email_changes_response = {
-
account_id : id; (** The account changes are for *)
-
old_state : string; (** The state provided in the request *)
-
new_state : string; (** The current state on the server *)
-
has_more_changes : bool; (** If true, more changes are available *)
-
created : id list; (** IDs of emails created since old_state *)
-
updated : id list; (** IDs of emails updated since old_state *)
-
destroyed : id list; (** IDs of emails destroyed since old_state *)
-
}
-
-
(** Email/query request arguments as defined in RFC8621 Section 4.4.
-
Used to query emails based on filter criteria.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.4>
-
*)
-
type email_query_arguments = {
-
account_id : id; (** The account to query *)
-
filter : email_query_filter option; (** Filter to match emails against *)
-
sort : comparator list option; (** Sort criteria *)
-
collapse_threads : bool option; (** Whether to collapse threads in the results *)
-
position : unsigned_int option; (** Zero-based index of first result to return *)
-
anchor : id option; (** ID of email to use as reference point *)
-
anchor_offset : int_t option; (** Offset from anchor to start returning results *)
-
limit : unsigned_int option; (** Maximum number of results to return *)
-
calculate_total : bool option; (** Whether to calculate the total number of matching emails *)
-
}
-
-
(** Email/query response as defined in RFC8621 Section 4.4.
-
Contains IDs of emails matching the query.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.4>
-
*)
-
type email_query_response = {
-
account_id : id; (** The account that was queried *)
-
query_state : string; (** State string for the query results *)
-
can_calculate_changes : bool; (** Whether queryChanges can be used with these results *)
-
position : unsigned_int; (** Zero-based index of the first result *)
-
ids : id list; (** IDs of emails matching the query *)
-
total : unsigned_int option; (** Total number of matches if requested *)
-
thread_ids : id list option; (** IDs of threads if collapse_threads was true *)
-
}
-
-
(** Email/queryChanges request arguments as defined in RFC8621 Section 4.7.
-
Used to get changes to email query results.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.7>
-
*)
-
type email_query_changes_arguments = {
-
account_id : id; (** The account to query *)
-
filter : email_query_filter option; (** Same filter as the original query *)
-
sort : comparator list option; (** Same sort as the original query *)
-
collapse_threads : bool option; (** Same collapse_threads as the original query *)
-
since_query_state : string; (** The query_state from the previous result *)
-
max_changes : unsigned_int option; (** Maximum number of changes to return *)
-
up_to_id : id option; (** ID of the last email to check for changes *)
-
}
-
-
(** Email/queryChanges response as defined in RFC8621 Section 4.7.
-
Reports changes to an email query since the previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.7>
-
*)
-
type email_query_changes_response = {
-
account_id : id; (** The account that was queried *)
-
old_query_state : string; (** The query_state from the request *)
-
new_query_state : string; (** The current query_state on the server *)
-
total : unsigned_int option; (** Updated total number of matches, if requested *)
-
removed : id list; (** IDs that were in the old results but not the new *)
-
added : email_query_changes_added list; (** IDs that are in the new results but not the old *)
-
}
-
-
(** Added item in email query changes as defined in RFC8621 Section 4.7.
-
Represents an email added to query results.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.7>
-
*)
-
and email_query_changes_added = {
-
id : id; (** ID of the added email *)
-
index : unsigned_int; (** Zero-based index of the added email in the results *)
-
}
-
-
(** Email/set request arguments as defined in RFC8621 Section 4.8.
-
Used to create, update, and destroy emails.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.8>
-
*)
-
type email_set_arguments = {
-
account_id : id; (** The account to make changes in *)
-
if_in_state : string option; (** Only apply changes if in this state *)
-
create : (id * email_creation) list option; (** Map of creation IDs to emails to create *)
-
update : (id * email_update) list option; (** Map of IDs to update properties *)
-
destroy : id list option; (** List of IDs to destroy *)
-
}
-
-
(** Properties for email creation as defined in RFC8621 Section 4.8.
-
Used to create new emails.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.8>
-
*)
-
and email_creation = {
-
mailbox_ids : (id * bool) list; (** Map of mailbox IDs to boolean (whether message belongs to mailbox) *)
-
keywords : (keyword * bool) list option; (** Map of keywords to boolean (whether message has keyword) *)
-
received_at : utc_date option; (** When the message was received by the server *)
-
message_id : string list option; (** Message-ID header values *)
-
in_reply_to : string list option; (** In-Reply-To header values *)
-
references : string list option; (** References header values *)
-
sender : email_address list option; (** Sender header addresses *)
-
from : email_address list option; (** From header addresses *)
-
to_ : email_address list option; (** To header addresses *)
-
cc : email_address list option; (** Cc header addresses *)
-
bcc : email_address list option; (** Bcc header addresses *)
-
reply_to : email_address list option; (** Reply-To header addresses *)
-
subject : string option; (** Subject header value *)
-
body_values : (string * string) list option; (** Map of part IDs to text content *)
-
text_body : email_body_part list option; (** Plain text message body parts *)
-
html_body : email_body_part list option; (** HTML message body parts *)
-
attachments : email_body_part list option; (** Attachment parts in the message *)
-
headers : header list option; (** All headers in the message *)
-
}
-
-
(** Properties for email update as defined in RFC8621 Section 4.8.
-
Used to update existing emails.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.8>
-
*)
-
and email_update = {
-
keywords : (keyword * bool) list option; (** New keywords to set on the email *)
-
mailbox_ids : (id * bool) list option; (** New mailboxes to set for the email *)
-
}
-
-
(** Email/set response as defined in RFC8621 Section 4.8.
-
Reports the results of email changes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.8>
-
*)
-
type email_set_response = {
-
account_id : id; (** The account that was modified *)
-
old_state : string option; (** The state before processing, if changed *)
-
new_state : string; (** The current state on the server *)
-
created : (id * email) list option; (** Map of creation IDs to created emails *)
-
updated : id list option; (** List of IDs that were successfully updated *)
-
destroyed : id list option; (** List of IDs that were successfully destroyed *)
-
not_created : (id * set_error) list option; (** Map of IDs to errors for failed creates *)
-
not_updated : (id * set_error) list option; (** Map of IDs to errors for failed updates *)
-
not_destroyed : (id * set_error) list option; (** Map of IDs to errors for failed destroys *)
-
}
-
-
(** Email/copy request arguments as defined in RFC8621 Section 4.9.
-
Used to copy emails between accounts.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.9>
-
*)
-
type email_copy_arguments = {
-
from_account_id : id; (** The account to copy emails from *)
-
account_id : id; (** The account to copy emails to *)
-
create : (id * email_creation) list; (** Map of creation IDs to email creation properties *)
-
on_success_destroy_original : bool option; (** Whether to destroy originals after copying *)
-
}
-
-
(** Email/copy response as defined in RFC8621 Section 4.9.
-
Reports the results of copying emails.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.9>
-
*)
-
type email_copy_response = {
-
from_account_id : id; (** The account emails were copied from *)
-
account_id : id; (** The account emails were copied to *)
-
created : (id * email) list option; (** Map of creation IDs to created emails *)
-
not_created : (id * set_error) list option; (** Map of IDs to errors for failed copies *)
-
}
-
-
(** Email/import request arguments as defined in RFC8621 Section 4.10.
-
Used to import raw emails from blobs.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.10>
-
*)
-
type email_import_arguments = {
-
account_id : id; (** The account to import emails into *)
-
emails : (id * email_import) list; (** Map of creation IDs to import properties *)
-
}
-
-
(** Properties for email import as defined in RFC8621 Section 4.10.
-
Used to import raw emails from blobs.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.10>
-
*)
-
and email_import = {
-
blob_id : id; (** ID of the blob containing the raw message *)
-
mailbox_ids : (id * bool) list; (** Map of mailbox IDs to boolean (whether message belongs to mailbox) *)
-
keywords : (keyword * bool) list option; (** Map of keywords to boolean (whether message has keyword) *)
-
received_at : utc_date option; (** When the message was received, defaults to now *)
-
}
-
-
(** Email/import response as defined in RFC8621 Section 4.10.
-
Reports the results of importing emails.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.10>
-
*)
-
type email_import_response = {
-
account_id : id; (** The account emails were imported into *)
-
created : (id * email) list option; (** Map of creation IDs to created emails *)
-
not_created : (id * set_error) list option; (** Map of IDs to errors for failed imports *)
-
}
-
-
(** {1:search_snippet Search snippets}
-
Search snippet types as defined in RFC8621 Section 4.11
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.11>
-
*)
-
-
(** SearchSnippet/get request arguments as defined in RFC8621 Section 4.11.
-
Used to get highlighted snippets from emails matching a search.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.11>
-
*)
-
type search_snippet_get_arguments = {
-
account_id : id; (** The account to search in *)
-
email_ids : id list; (** The IDs of emails to get snippets for *)
-
filter : email_filter_condition; (** Filter containing the text to find and highlight *)
-
}
-
-
(** SearchSnippet/get response as defined in RFC8621 Section 4.11.
-
Contains search result snippets with highlighted text.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.11>
-
*)
-
type search_snippet_get_response = {
-
account_id : id; (** The account that was searched *)
-
list : (id * search_snippet) list; (** Map of email IDs to their search snippets *)
-
not_found : id list; (** IDs for which no snippet could be generated *)
-
}
-
-
(** Search snippet for an email as defined in RFC8621 Section 4.11.
-
Contains highlighted parts of emails matching a search.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-4.11>
-
*)
-
and search_snippet = {
-
subject : string option; (** Subject with search terms highlighted *)
-
preview : string option; (** Email body preview with search terms highlighted *)
-
}
-
-
(** {1:submission EmailSubmission objects}
-
Email submission types as defined in RFC8621 Section 5
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5>
-
*)
-
-
(** EmailSubmission address as defined in RFC8621 Section 5.1.
-
Represents an email address for mail submission.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.1>
-
*)
-
type submission_address = {
-
email : string; (** The email address (e.g., "john@example.com") *)
-
parameters : (string * string) list option; (** SMTP extension parameters *)
-
}
-
-
(** Email submission object as defined in RFC8621 Section 5.1.
-
Represents an email that has been or will be sent.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.1>
-
*)
-
type email_submission = {
-
id : id; (** Server-assigned ID for the submission *)
-
identity_id : id; (** ID of the identity used to send the email *)
-
email_id : id; (** ID of the email to send *)
-
thread_id : id; (** ID of the thread containing the message *)
-
envelope : envelope option; (** SMTP envelope for the message *)
-
send_at : utc_date option; (** When to send the email, null for immediate *)
-
undo_status : [
-
| `pending (** Submission can still be canceled *)
-
| `final (** Submission can no longer be canceled *)
-
| `canceled (** Submission was canceled *)
-
] option; (** Current undo status of the submission *)
-
delivery_status : (string * submission_status) list option; (** Map of recipient to delivery status *)
-
dsn_blob_ids : (string * id) list option; (** Map of recipient to DSN blob ID *)
-
mdn_blob_ids : (string * id) list option; (** Map of recipient to MDN blob ID *)
-
}
-
-
(** Envelope for mail submission as defined in RFC8621 Section 5.1.
-
Represents the SMTP envelope for a message.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.1>
-
*)
-
and envelope = {
-
mail_from : submission_address; (** Return path for the message *)
-
rcpt_to : submission_address list; (** Recipients for the message *)
-
}
-
-
(** Delivery status for submitted email as defined in RFC8621 Section 5.1.
-
Represents the SMTP status of a delivery attempt.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.1>
-
*)
-
and submission_status = {
-
smtp_reply : string; (** SMTP response from the server *)
-
delivered : string option; (** Timestamp when message was delivered, if successful *)
-
}
-
-
(** EmailSubmission/get request arguments as defined in RFC8621 Section 5.3.
-
Used to fetch email submissions by ID.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.3>
-
*)
-
type email_submission_get_arguments = {
-
account_id : id; (** The account to fetch submissions from *)
-
ids : id list option; (** The IDs of submissions to fetch, null means all *)
-
properties : string list option; (** Properties to return, null means all *)
-
}
-
-
(** EmailSubmission/get response as defined in RFC8621 Section 5.3.
-
Contains requested email submissions.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.3>
-
*)
-
type email_submission_get_response = {
-
account_id : id; (** The account from which submissions were fetched *)
-
state : string; (** A string representing the state on the server *)
-
list : email_submission list; (** The list of submissions requested *)
-
not_found : id list; (** IDs requested that could not be found *)
-
}
-
-
(** EmailSubmission/changes request arguments as defined in RFC8621 Section 5.4.
-
Used to get submission changes since a previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.4>
-
*)
-
type email_submission_changes_arguments = {
-
account_id : id; (** The account to get changes for *)
-
since_state : string; (** The previous state to compare to *)
-
max_changes : unsigned_int option; (** Maximum number of changes to return *)
-
}
-
-
(** EmailSubmission/changes response as defined in RFC8621 Section 5.4.
-
Reports submissions that have changed since a previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.4>
-
*)
-
type email_submission_changes_response = {
-
account_id : id; (** The account changes are for *)
-
old_state : string; (** The state provided in the request *)
-
new_state : string; (** The current state on the server *)
-
has_more_changes : bool; (** If true, more changes are available *)
-
created : id list; (** IDs of submissions created since old_state *)
-
updated : id list; (** IDs of submissions updated since old_state *)
-
destroyed : id list; (** IDs of submissions destroyed since old_state *)
-
}
-
-
(** EmailSubmission/query filter condition as defined in RFC8621 Section 5.5.
-
Specifies conditions for filtering email submissions in queries.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.5>
-
*)
-
type email_submission_filter_condition = {
-
identity_id : id option; (** Only include submissions with this identity *)
-
email_id : id option; (** Only include submissions for this email *)
-
thread_id : id option; (** Only include submissions for emails in this thread *)
-
before : utc_date option; (** Only include submissions created before this date-time *)
-
after : utc_date option; (** Only include submissions created after this date-time *)
-
subject : string option; (** Only include submissions with matching subjects *)
-
}
-
-
(** Filter for email submission queries as defined in RFC8621 Section 5.5.
-
Complex filter for EmailSubmission/query method.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.5>
-
*)
-
type email_submission_query_filter = [
-
| `And of email_submission_query_filter list (** Logical AND of filters *)
-
| `Or of email_submission_query_filter list (** Logical OR of filters *)
-
| `Not of email_submission_query_filter (** Logical NOT of a filter *)
-
| `Condition of email_submission_filter_condition (** Simple condition filter *)
-
]
-
-
(** EmailSubmission/query request arguments as defined in RFC8621 Section 5.5.
-
Used to query email submissions based on filter criteria.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.5>
-
*)
-
type email_submission_query_arguments = {
-
account_id : id; (** The account to query *)
-
filter : email_submission_query_filter option; (** Filter to match submissions against *)
-
sort : comparator list option; (** Sort criteria *)
-
position : unsigned_int option; (** Zero-based index of first result to return *)
-
anchor : id option; (** ID of submission to use as reference point *)
-
anchor_offset : int_t option; (** Offset from anchor to start returning results *)
-
limit : unsigned_int option; (** Maximum number of results to return *)
-
calculate_total : bool option; (** Whether to calculate the total number of matching submissions *)
-
}
-
-
(** EmailSubmission/query response as defined in RFC8621 Section 5.5.
-
Contains IDs of email submissions matching the query.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.5>
-
*)
-
type email_submission_query_response = {
-
account_id : id; (** The account that was queried *)
-
query_state : string; (** State string for the query results *)
-
can_calculate_changes : bool; (** Whether queryChanges can be used with these results *)
-
position : unsigned_int; (** Zero-based index of the first result *)
-
ids : id list; (** IDs of email submissions matching the query *)
-
total : unsigned_int option; (** Total number of matches if requested *)
-
}
-
-
(** EmailSubmission/set request arguments as defined in RFC8621 Section 5.6.
-
Used to create, update, and destroy email submissions.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.6>
-
*)
-
type email_submission_set_arguments = {
-
account_id : id; (** The account to make changes in *)
-
if_in_state : string option; (** Only apply changes if in this state *)
-
create : (id * email_submission_creation) list option; (** Map of creation IDs to submissions to create *)
-
update : (id * email_submission_update) list option; (** Map of IDs to update properties *)
-
destroy : id list option; (** List of IDs to destroy *)
-
on_success_update_email : (id * email_update) list option; (** Emails to update if submissions succeed *)
-
}
-
-
(** Properties for email submission creation as defined in RFC8621 Section 5.6.
-
Used to create new email submissions.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.6>
-
*)
-
and email_submission_creation = {
-
email_id : id; (** ID of the email to send *)
-
identity_id : id; (** ID of the identity to send from *)
-
envelope : envelope option; (** Custom envelope, if needed *)
-
send_at : utc_date option; (** When to send the email, defaults to now *)
-
}
-
-
(** Properties for email submission update as defined in RFC8621 Section 5.6.
-
Used to update existing email submissions.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.6>
-
*)
-
and email_submission_update = {
-
email_id : id option; (** New email ID to use for this submission *)
-
identity_id : id option; (** New identity ID to use for this submission *)
-
envelope : envelope option; (** New envelope to use for this submission *)
-
undo_status : [`canceled] option; (** Set to cancel a pending submission *)
-
}
-
-
(** EmailSubmission/set response as defined in RFC8621 Section 5.6.
-
Reports the results of email submission changes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-5.6>
-
*)
-
type email_submission_set_response = {
-
account_id : id; (** The account that was modified *)
-
old_state : string option; (** The state before processing, if changed *)
-
new_state : string; (** The current state on the server *)
-
created : (id * email_submission) list option; (** Map of creation IDs to created submissions *)
-
updated : id list option; (** List of IDs that were successfully updated *)
-
destroyed : id list option; (** List of IDs that were successfully destroyed *)
-
not_created : (id * set_error) list option; (** Map of IDs to errors for failed creates *)
-
not_updated : (id * set_error) list option; (** Map of IDs to errors for failed updates *)
-
not_destroyed : (id * set_error) list option; (** Map of IDs to errors for failed destroys *)
-
}
-
-
(** {1:identity Identity objects}
-
Identity types as defined in RFC8621 Section 6
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-6>
-
*)
-
-
(** Identity for sending mail as defined in RFC8621 Section 6.
-
Represents an email identity that can be used to send messages.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-6>
-
*)
-
type identity = {
-
id : id; (** Server-assigned ID for the identity *)
-
name : string; (** Display name for the identity *)
-
email : string; (** Email address for the identity *)
-
reply_to : email_address list option; (** Reply-To addresses to use when sending *)
-
bcc : email_address list option; (** BCC addresses to automatically include *)
-
text_signature : string option; (** Plain text signature for the identity *)
-
html_signature : string option; (** HTML signature for the identity *)
-
may_delete : bool; (** Whether this identity can be deleted *)
-
}
-
-
(** Identity/get request arguments as defined in RFC8621 Section 6.1.
-
Used to fetch identities by ID.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-6.1>
-
*)
-
type identity_get_arguments = {
-
account_id : id; (** The account to fetch identities from *)
-
ids : id list option; (** The IDs of identities to fetch, null means all *)
-
properties : string list option; (** Properties to return, null means all *)
-
}
-
-
(** Identity/get response as defined in RFC8621 Section 6.1.
-
Contains requested identities.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-6.1>
-
*)
-
type identity_get_response = {
-
account_id : id; (** The account from which identities were fetched *)
-
state : string; (** A string representing the state on the server *)
-
list : identity list; (** The list of identities requested *)
-
not_found : id list; (** IDs requested that could not be found *)
-
}
-
-
(** Identity/changes request arguments as defined in RFC8621 Section 6.2.
-
Used to get identity changes since a previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-6.2>
-
*)
-
type identity_changes_arguments = {
-
account_id : id; (** The account to get changes for *)
-
since_state : string; (** The previous state to compare to *)
-
max_changes : unsigned_int option; (** Maximum number of changes to return *)
-
}
-
-
(** Identity/changes response as defined in RFC8621 Section 6.2.
-
Reports identities that have changed since a previous state.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-6.2>
-
*)
-
type identity_changes_response = {
-
account_id : id; (** The account changes are for *)
-
old_state : string; (** The state provided in the request *)
-
new_state : string; (** The current state on the server *)
-
has_more_changes : bool; (** If true, more changes are available *)
-
created : id list; (** IDs of identities created since old_state *)
-
updated : id list; (** IDs of identities updated since old_state *)
-
destroyed : id list; (** IDs of identities destroyed since old_state *)
-
}
-
-
(** Identity/set request arguments as defined in RFC8621 Section 6.3.
-
Used to create, update, and destroy identities.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-6.3>
-
*)
-
type identity_set_arguments = {
-
account_id : id; (** The account to make changes in *)
-
if_in_state : string option; (** Only apply changes if in this state *)
-
create : (id * identity_creation) list option; (** Map of creation IDs to identities to create *)
-
update : (id * identity_update) list option; (** Map of IDs to update properties *)
-
destroy : id list option; (** List of IDs to destroy *)
-
}
-
-
(** Properties for identity creation as defined in RFC8621 Section 6.3.
-
Used to create new identities.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-6.3>
-
*)
-
and identity_creation = {
-
name : string; (** Display name for the identity *)
-
email : string; (** Email address for the identity *)
-
reply_to : email_address list option; (** Reply-To addresses to use when sending *)
-
bcc : email_address list option; (** BCC addresses to automatically include *)
-
text_signature : string option; (** Plain text signature for the identity *)
-
html_signature : string option; (** HTML signature for the identity *)
-
}
-
-
(** Properties for identity update as defined in RFC8621 Section 6.3.
-
Used to update existing identities.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-6.3>
-
*)
-
and identity_update = {
-
name : string option; (** New display name for the identity *)
-
email : string option; (** New email address for the identity *)
-
reply_to : email_address list option; (** New Reply-To addresses to use *)
-
bcc : email_address list option; (** New BCC addresses to automatically include *)
-
text_signature : string option; (** New plain text signature *)
-
html_signature : string option; (** New HTML signature *)
-
}
-
-
(** Identity/set response as defined in RFC8621 Section 6.3.
-
Reports the results of identity changes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-6.3>
-
*)
-
type identity_set_response = {
-
account_id : id; (** The account that was modified *)
-
old_state : string option; (** The state before processing, if changed *)
-
new_state : string; (** The current state on the server *)
-
created : (id * identity) list option; (** Map of creation IDs to created identities *)
-
updated : id list option; (** List of IDs that were successfully updated *)
-
destroyed : id list option; (** List of IDs that were successfully destroyed *)
-
not_created : (id * set_error) list option; (** Map of IDs to errors for failed creates *)
-
not_updated : (id * set_error) list option; (** Map of IDs to errors for failed updates *)
-
not_destroyed : (id * set_error) list option; (** Map of IDs to errors for failed destroys *)
-
}
-
-
(** {1:vacation_response VacationResponse objects}
-
Vacation response types as defined in RFC8621 Section 7
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-7>
-
*)
-
-
(** Vacation auto-reply setting as defined in RFC8621 Section 7.
-
Represents an automatic vacation/out-of-office response.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-7>
-
*)
-
type vacation_response = {
-
id : id; (** Server-assigned ID for the vacation response *)
-
is_enabled : bool; (** Whether the vacation response is active *)
-
from_date : utc_date option; (** Start date-time of the vacation period *)
-
to_date : utc_date option; (** End date-time of the vacation period *)
-
subject : string option; (** Subject line for the vacation response *)
-
text_body : string option; (** Plain text body for the vacation response *)
-
html_body : string option; (** HTML body for the vacation response *)
-
}
-
-
(** VacationResponse/get request arguments as defined in RFC8621 Section 7.2.
-
Used to fetch vacation responses by ID.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-7.2>
-
*)
-
type vacation_response_get_arguments = {
-
account_id : id; (** The account to fetch vacation responses from *)
-
ids : id list option; (** The IDs of vacation responses to fetch, null means all *)
-
properties : string list option; (** Properties to return, null means all *)
-
}
-
-
(** VacationResponse/get response as defined in RFC8621 Section 7.2.
-
Contains requested vacation responses.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-7.2>
-
*)
-
type vacation_response_get_response = {
-
account_id : id; (** The account from which vacation responses were fetched *)
-
state : string; (** A string representing the state on the server *)
-
list : vacation_response list; (** The list of vacation responses requested *)
-
not_found : id list; (** IDs requested that could not be found *)
-
}
-
-
(** VacationResponse/set request arguments as defined in RFC8621 Section 7.3.
-
Used to update vacation responses.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-7.3>
-
*)
-
type vacation_response_set_arguments = {
-
account_id : id; (** The account to make changes in *)
-
if_in_state : string option; (** Only apply changes if in this state *)
-
update : (id * vacation_response_update) list; (** Map of IDs to update properties *)
-
}
-
-
(** Properties for vacation response update as defined in RFC8621 Section 7.3.
-
Used to update existing vacation responses.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-7.3>
-
*)
-
and vacation_response_update = {
-
is_enabled : bool option; (** Whether the vacation response is active *)
-
from_date : utc_date option; (** Start date-time of the vacation period *)
-
to_date : utc_date option; (** End date-time of the vacation period *)
-
subject : string option; (** Subject line for the vacation response *)
-
text_body : string option; (** Plain text body for the vacation response *)
-
html_body : string option; (** HTML body for the vacation response *)
-
}
-
-
(** VacationResponse/set response as defined in RFC8621 Section 7.3.
-
Reports the results of vacation response changes.
-
@see <https://datatracker.ietf.org/doc/html/rfc8621#section-7.3>
-
*)
-
type vacation_response_set_response = {
-
account_id : id; (** The account that was modified *)
-
old_state : string option; (** The state before processing, if changed *)
-
new_state : string; (** The current state on the server *)
-
updated : id list option; (** List of IDs that were successfully updated *)
-
not_updated : (id * set_error) list option; (** Map of IDs to errors for failed updates *)
-
}
-
-
(** {1:message_flags Message Flags and Mailbox Attributes}
-
Message flag types as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02
-
@see <https://datatracker.ietf.org/doc/html/draft-ietf-mailmaint-messageflag-mailboxattribute>
-
*)
-
-
(** Flag color defined by the combination of MailFlagBit0, MailFlagBit1, and MailFlagBit2 keywords
-
as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 Section 3.
-
@see <https://datatracker.ietf.org/doc/html/draft-ietf-mailmaint-messageflag-mailboxattribute#section-3>
-
*)
-
type flag_color =
-
| Red (** Bit pattern 000 - default color *)
-
| Orange (** Bit pattern 100 - MailFlagBit2 set *)
-
| Yellow (** Bit pattern 010 - MailFlagBit1 set *)
-
| Green (** Bit pattern 111 - all bits set *)
-
| Blue (** Bit pattern 001 - MailFlagBit0 set *)
-
| Purple (** Bit pattern 101 - MailFlagBit2 and MailFlagBit0 set *)
-
| Gray (** Bit pattern 011 - MailFlagBit1 and MailFlagBit0 set *)
-
-
(** Standard message keywords as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 Section 4.1.
-
These are standardized keywords that can be applied to email messages.
-
@see <https://datatracker.ietf.org/doc/html/draft-ietf-mailmaint-messageflag-mailboxattribute#section-4.1>
-
*)
-
type message_keyword =
-
| Notify (** Indicate a notification should be shown for this message *)
-
| Muted (** User is not interested in future replies to this thread *)
-
| Followed (** User is particularly interested in future replies to this thread *)
-
| Memo (** Message is a note-to-self about another message in the same thread *)
-
| HasMemo (** Message has an associated memo with the $memo keyword *)
-
| HasAttachment (** Message has an attachment *)
-
| HasNoAttachment (** Message does not have an attachment *)
-
| AutoSent (** Message was sent automatically as a response due to a user rule *)
-
| Unsubscribed (** User has unsubscribed from the thread this message is in *)
-
| CanUnsubscribe (** Message has an RFC8058-compliant List-Unsubscribe header *)
-
| Imported (** Message was imported from another mailbox *)
-
| IsTrusted (** Server has verified authenticity of the from name and email *)
-
| MaskedEmail (** Message was received via an alias created for an individual sender *)
-
| New (** Message should be made more prominent due to a recent action *)
-
| MailFlagBit0 (** Bit 0 of the 3-bit flag color pattern *)
-
| MailFlagBit1 (** Bit 1 of the 3-bit flag color pattern *)
-
| MailFlagBit2 (** Bit 2 of the 3-bit flag color pattern *)
-
| OtherKeyword of string (** Other non-standard keywords *)
-
-
(** Special mailbox attribute names as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 Section 4.2.
-
These are standardized attributes for special-purpose mailboxes.
-
@see <https://datatracker.ietf.org/doc/html/draft-ietf-mailmaint-messageflag-mailboxattribute#section-4.2>
-
*)
-
type mailbox_attribute =
-
| Snoozed (** Mailbox containing messages that have been snoozed *)
-
| Scheduled (** Mailbox containing messages scheduled to be sent later *)
-
| Memos (** Mailbox containing messages with the $memo keyword *)
-
| OtherAttribute of string (** Other non-standard mailbox attributes *)
-
-
(** Convert bit values to a flag color
-
@param bit0 Value of bit 0 (least significant bit)
-
@param bit1 Value of bit 1
-
@param bit2 Value of bit 2 (most significant bit)
-
@return The corresponding flag color
-
*)
-
val flag_color_of_bits : bool -> bool -> bool -> flag_color
-
-
(** Get the bit values for a flag color
-
@param color The flag color
-
@return Tuple of (bit2, bit1, bit0) values
-
*)
-
val bits_of_flag_color : flag_color -> bool * bool * bool
-
-
(** Check if a message has a flag color based on its keywords
-
@param keywords The list of keywords for the message
-
@return True if the message has one or more flag color bits set
-
*)
-
val has_flag_color : (keyword * bool) list -> bool
-
-
(** Get the flag color from a message's keywords, if present
-
@param keywords The list of keywords for the message
-
@return The flag color if all required bits are present, None otherwise
-
*)
-
val get_flag_color : (keyword * bool) list -> flag_color option
-
-
(** Convert a message keyword to its string representation
-
@param keyword The message keyword
-
@return String representation with $ prefix (e.g., "$notify")
-
*)
-
val string_of_message_keyword : message_keyword -> string
-
-
(** Parse a string into a message keyword
-
@param s The string to parse (with or without $ prefix)
-
@return The corresponding message keyword
-
*)
-
val message_keyword_of_string : string -> message_keyword
-
-
(** Convert a mailbox attribute to its string representation
-
@param attr The mailbox attribute
-
@return String representation with $ prefix (e.g., "$snoozed")
-
*)
-
val string_of_mailbox_attribute : mailbox_attribute -> string
-
-
(** Parse a string into a mailbox attribute
-
@param s The string to parse (with or without $ prefix)
-
@return The corresponding mailbox attribute
-
*)
-
val mailbox_attribute_of_string : string -> mailbox_attribute
-
-
(** Get a human-readable representation of a flag color
-
@param color The flag color
-
@return Human-readable name of the color
-
*)
-
val human_readable_flag_color : flag_color -> string
-
-
(** Get a human-readable representation of a message keyword
-
@param keyword The message keyword
-
@return Human-readable description of the keyword
-
*)
-
val human_readable_message_keyword : message_keyword -> string
-
-
(** Format email keywords into a human-readable string representation
-
@param keywords The list of keywords and their values
-
@return Human-readable comma-separated list of keywords
-
*)
-
val format_email_keywords : (keyword * bool) list -> string
-
end
-
-
(** {1 JSON serialization}
-
Functions for serializing and deserializing JMAP Mail objects to/from JSON
-
*)
-
-
module Json : sig
-
open Types
-
-
(** {2 Helper functions for serialization}
-
Utility functions for converting between OCaml types and JSON representation
-
*)
-
-
(** Convert a mailbox role to its string representation
-
@param role The mailbox role
-
@return String representation (e.g., "inbox", "drafts", etc.)
-
*)
-
val string_of_mailbox_role : mailbox_role -> string
-
-
(** Parse a string into a mailbox role
-
@param s The string to parse
-
@return The corresponding mailbox role, or Unknown if not recognized
-
*)
-
val mailbox_role_of_string : string -> mailbox_role
-
-
(** Convert an email keyword to its string representation
-
@param keyword The email keyword
-
@return String representation with $ prefix (e.g., "$flagged")
-
*)
-
val string_of_keyword : keyword -> string
-
-
(** Parse a string into an email keyword
-
@param s The string to parse (with or without $ prefix)
-
@return The corresponding email keyword
-
*)
-
val keyword_of_string : string -> keyword
-
-
(** {2 Mailbox serialization}
-
Functions for serializing and deserializing mailbox objects
-
*)
-
-
(** TODO:claude - Need to implement all JSON serialization functions
-
for each type we've defined. This would be a substantial amount of
-
code and likely require additional understanding of the ezjsonm API.
-
-
The interface would include functions like:
-
-
val mailbox_to_json : mailbox -> Ezjsonm.value
-
val mailbox_of_json : Ezjsonm.value -> mailbox result
-
-
And similarly for all other types.
-
*)
-
end
-
-
(** {1 API functions}
-
High-level functions for interacting with JMAP Mail servers
-
*)
-
-
(** Authentication credentials for a JMAP server *)
-
type credentials = {
-
username: string; (** Username for authentication *)
-
password: string; (** Password for authentication *)
-
}
-
-
(** Connection to a JMAP mail server *)
-
type connection = {
-
session: Jmap.Types.session; (** Session information from the server *)
-
config: Jmap.Api.config; (** Configuration for API requests *)
-
}
-
-
(** Login to a JMAP server and establish a connection
-
@param uri The URI of the JMAP server
-
@param credentials Authentication credentials
-
@return A connection object if successful
-
-
Creates a new connection to a JMAP server using username/password authentication.
-
*)
-
val login :
-
uri:string ->
-
credentials:credentials ->
-
(connection, Jmap.Api.error) result Lwt.t
-
-
(** Login to a JMAP server using an API token
-
@param uri The URI of the JMAP server
-
@param api_token The API token for authentication
-
@return A connection object if successful
-
-
Creates a new connection to a JMAP server using Bearer token authentication.
-
*)
-
val login_with_token :
-
uri:string ->
-
api_token:string ->
-
(connection, Jmap.Api.error) result Lwt.t
-
-
(** Get all mailboxes for an account
-
@param conn The JMAP connection
-
@param account_id The account ID to get mailboxes for
-
@return A list of mailboxes if successful
-
-
Retrieves all mailboxes (folders) in the specified account.
-
*)
-
val get_mailboxes :
-
connection ->
-
account_id:Jmap.Types.id ->
-
(Types.mailbox list, Jmap.Api.error) result Lwt.t
-
-
(** Get a specific mailbox by ID
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param mailbox_id The mailbox ID to retrieve
-
@return The mailbox if found
-
-
Retrieves a single mailbox by its ID.
-
*)
-
val get_mailbox :
-
connection ->
-
account_id:Jmap.Types.id ->
-
mailbox_id:Jmap.Types.id ->
-
(Types.mailbox, Jmap.Api.error) result Lwt.t
-
-
(** Get messages in a mailbox
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param mailbox_id The mailbox ID to get messages from
-
@param limit Optional limit on number of messages to return
-
@return The list of email messages if successful
-
-
Retrieves email messages in the specified mailbox, with optional limit.
-
*)
-
val get_messages_in_mailbox :
-
connection ->
-
account_id:Jmap.Types.id ->
-
mailbox_id:Jmap.Types.id ->
-
?limit:int ->
-
unit ->
-
(Types.email list, Jmap.Api.error) result Lwt.t
-
-
(** Get a single email message by ID
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param email_id The email ID to retrieve
-
@return The email message if found
-
-
Retrieves a single email message by its ID.
-
*)
-
val get_email :
-
connection ->
-
account_id:Jmap.Types.id ->
-
email_id:Jmap.Types.id ->
-
(Types.email, Jmap.Api.error) result Lwt.t
-
-
(** Check if an email has a specific message keyword
-
@param email The email to check
-
@param keyword The message keyword to look for
-
@return true if the email has the keyword, false otherwise
-
-
Tests whether an email has a particular keyword (flag) set.
-
*)
-
val has_message_keyword :
-
Types.email ->
-
Types.message_keyword ->
-
bool
-
-
(** Add a message keyword to an email
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param email_id The email ID
-
@param keyword The message keyword to add
-
@return Success or error
-
-
Adds a keyword (flag) to an email message.
-
*)
-
val add_message_keyword :
-
connection ->
-
account_id:Jmap.Types.id ->
-
email_id:Jmap.Types.id ->
-
keyword:Types.message_keyword ->
-
(unit, Jmap.Api.error) result Lwt.t
-
-
(** Set a flag color for an email
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param email_id The email ID
-
@param color The flag color to set
-
@return Success or error
-
-
Sets a flag color on an email message by setting the appropriate bit flags.
-
*)
-
val set_flag_color :
-
connection ->
-
account_id:Jmap.Types.id ->
-
email_id:Jmap.Types.id ->
-
color:Types.flag_color ->
-
(unit, Jmap.Api.error) result Lwt.t
-
-
(** Convert an email's keywords to typed message_keyword list
-
@param email The email to analyze
-
@return List of message keywords
-
-
Extracts all message keywords from an email's keyword list.
-
*)
-
val get_message_keywords :
-
Types.email ->
-
Types.message_keyword list
-
-
(** Get emails with a specific message keyword
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param keyword The message keyword to search for
-
@param limit Optional limit on number of emails to return
-
@return List of emails with the keyword if successful
-
-
Retrieves all emails that have a specific keyword (flag) set.
-
*)
-
val get_emails_with_keyword :
-
connection ->
-
account_id:Jmap.Types.id ->
-
keyword:Types.message_keyword ->
-
?limit:int ->
-
unit ->
-
(Types.email list, Jmap.Api.error) result Lwt.t
-
-
(** {1 Email Submission}
-
Functions for sending emails
-
*)
-
-
(** Create a new email draft
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param mailbox_id The mailbox ID to store the draft in (usually "drafts")
-
@param from The sender's email address
-
@param to_addresses List of recipient email addresses
-
@param subject The email subject line
-
@param text_body Plain text message body
-
@param html_body Optional HTML message body
-
@return The created email ID if successful
-
-
Creates a new email draft in the specified mailbox with the provided content.
-
*)
-
val create_email_draft :
-
connection ->
-
account_id:Jmap.Types.id ->
-
mailbox_id:Jmap.Types.id ->
-
from:string ->
-
to_addresses:string list ->
-
subject:string ->
-
text_body:string ->
-
?html_body:string ->
-
unit ->
-
(Jmap.Types.id, Jmap.Api.error) result Lwt.t
-
-
(** Get all identities for an account
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@return A list of identities if successful
-
-
Retrieves all identities (email addresses that can be used for sending) for an account.
-
*)
-
val get_identities :
-
connection ->
-
account_id:Jmap.Types.id ->
-
(Types.identity list, Jmap.Api.error) result Lwt.t
-
-
(** Find a suitable identity by email address
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param email The email address to match
-
@return The identity if found, otherwise Error
-
-
Finds an identity that matches the given email address, either exactly or
-
via a wildcard pattern (e.g., *@domain.com).
-
*)
-
val find_identity_by_email :
-
connection ->
-
account_id:Jmap.Types.id ->
-
email:string ->
-
(Types.identity, Jmap.Api.error) result Lwt.t
-
-
(** Submit an email for delivery
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param identity_id The identity ID to send from
-
@param email_id The email ID to submit
-
@param envelope Optional custom envelope
-
@return The submission ID if successful
-
-
Submits an existing email (usually a draft) for delivery using the specified identity.
-
*)
-
val submit_email :
-
connection ->
-
account_id:Jmap.Types.id ->
-
identity_id:Jmap.Types.id ->
-
email_id:Jmap.Types.id ->
-
?envelope:Types.envelope ->
-
unit ->
-
(Jmap.Types.id, Jmap.Api.error) result Lwt.t
-
-
(** Create and submit an email in one operation
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param from The sender's email address
-
@param to_addresses List of recipient email addresses
-
@param subject The email subject line
-
@param text_body Plain text message body
-
@param html_body Optional HTML message body
-
@return The submission ID if successful
-
-
Creates a new email and immediately submits it for delivery.
-
This is a convenience function that combines create_email_draft and submit_email.
-
*)
-
val create_and_submit_email :
-
connection ->
-
account_id:Jmap.Types.id ->
-
from:string ->
-
to_addresses:string list ->
-
subject:string ->
-
text_body:string ->
-
?html_body:string ->
-
unit ->
-
(Jmap.Types.id, Jmap.Api.error) result Lwt.t
-
-
(** Get status of an email submission
-
@param conn The JMAP connection
-
@param account_id The account ID
-
@param submission_id The email submission ID
-
@return The submission status if successful
-
-
Retrieves the current status of an email submission, including delivery status if available.
-
*)
-
val get_submission_status :
-
connection ->
-
account_id:Jmap.Types.id ->
-
submission_id:Jmap.Types.id ->
-
(Types.email_submission, Jmap.Api.error) result Lwt.t
-
-
(** {1 Email Address Utilities}
-
Utilities for working with email addresses
-
*)
-
-
(** Check if an email address matches a filter string
-
@param email The email address to check
-
@param pattern The filter pattern to match against
-
@return True if the email address matches the filter
-
-
The filter supports simple wildcards:
-
- "*" matches any sequence of characters
-
- "?" matches any single character
-
- Case-insensitive matching is used
-
- If no wildcards are present, substring matching is used
-
*)
-
val email_address_matches : string -> string -> bool
-
-
(** Check if an email matches a sender filter
-
@param email The email object to check
-
@param pattern The sender filter pattern
-
@return True if any sender address matches the filter
-
-
Tests whether any of an email's sender addresses match the provided pattern.
-
*)
-
val email_matches_sender : Types.email -> string -> bool
···