this repo has no description
1(** Implementation of the JMAP Mail extension, as defined in RFC8621 *) 2 3(** Module for managing JMAP Mail-specific capability URIs *) 4module Capability = struct 5 (** Mail capability types *) 6 type mail = Mail 7 8 (** Mail capability URI *) 9 let mail_uri = "urn:ietf:params:jmap:mail" 10 11 (** Convert mail capability to URI string *) 12 let string_of_mail = function 13 | Mail -> mail_uri 14 15 (** Parse a string to mail capability *) 16 let mail_of_string = function 17 | s when s = mail_uri -> Some Mail 18 | _ -> None 19 20 (** Submission capability types *) 21 type submission = Submission 22 23 (** Submission capability URI *) 24 let submission_uri = "urn:ietf:params:jmap:submission" 25 26 (** Convert submission capability to URI string *) 27 let string_of_submission = function 28 | Submission -> submission_uri 29 30 (** Parse a string to submission capability *) 31 let submission_of_string = function 32 | s when s = submission_uri -> Some Submission 33 | _ -> None 34 35 (** Vacation response capability types *) 36 type vacation_response = VacationResponse 37 38 (** Vacation response capability URI *) 39 let vacation_response_uri = "urn:ietf:params:jmap:vacationresponse" 40 41 (** Convert vacation response capability to URI string *) 42 let string_of_vacation_response = function 43 | VacationResponse -> vacation_response_uri 44 45 (** Parse a string to vacation response capability *) 46 let vacation_response_of_string = function 47 | s when s = vacation_response_uri -> Some VacationResponse 48 | _ -> None 49 50 (** All mail extension capability types *) 51 type t = 52 | Mail of mail 53 | Submission of submission 54 | VacationResponse of vacation_response 55 | Extension of string 56 57 (** Convert capability to URI string *) 58 let to_string = function 59 | Mail m -> string_of_mail m 60 | Submission s -> string_of_submission s 61 | VacationResponse v -> string_of_vacation_response v 62 | Extension s -> s 63 64 (** Parse a string to a capability *) 65 let of_string s = 66 match mail_of_string s with 67 | Some m -> Mail m 68 | None -> 69 match submission_of_string s with 70 | Some s -> Submission s 71 | None -> 72 match vacation_response_of_string s with 73 | Some v -> VacationResponse v 74 | None -> Extension s 75 76 (** Check if a capability is a standard mail capability *) 77 let is_standard = function 78 | Mail _ | Submission _ | VacationResponse _ -> true 79 | Extension _ -> false 80 81 (** Check if a capability string is a standard mail capability *) 82 let is_standard_string s = 83 match of_string s with 84 | Extension _ -> false 85 | _ -> true 86 87 (** Create a list of capability strings *) 88 let strings_of_capabilities capabilities = 89 List.map to_string capabilities 90end 91 92module Types = struct 93 open Jmap.Types 94 95 (** {1 Mail capabilities} *) 96 97 (** Capability URI for JMAP Mail*) 98 let capability_mail = Capability.mail_uri 99 100 (** Capability URI for JMAP Submission *) 101 let capability_submission = Capability.submission_uri 102 103 (** Capability URI for JMAP Vacation Response *) 104 let capability_vacation_response = Capability.vacation_response_uri 105 106 (** {1:mailbox Mailbox objects} *) 107 108 (** A role for a mailbox. See RFC8621 Section 2. *) 109 type mailbox_role = 110 | All (** All mail *) 111 | Archive (** Archived mail *) 112 | Drafts (** Draft messages *) 113 | Flagged (** Starred/flagged mail *) 114 | Important (** Important mail *) 115 | Inbox (** Inbox *) 116 | Junk (** Spam/Junk mail *) 117 | Sent (** Sent mail *) 118 | Trash (** Deleted/Trash mail *) 119 | Unknown of string (** Server-specific roles *) 120 121 (** A mailbox (folder) in a mail account. See RFC8621 Section 2. *) 122 type mailbox = { 123 id : id; 124 name : string; 125 parent_id : id option; 126 role : mailbox_role option; 127 sort_order : unsigned_int; 128 total_emails : unsigned_int; 129 unread_emails : unsigned_int; 130 total_threads : unsigned_int; 131 unread_threads : unsigned_int; 132 is_subscribed : bool; 133 my_rights : mailbox_rights; 134 } 135 136 (** Rights for a mailbox. See RFC8621 Section 2. *) 137 and mailbox_rights = { 138 may_read_items : bool; 139 may_add_items : bool; 140 may_remove_items : bool; 141 may_set_seen : bool; 142 may_set_keywords : bool; 143 may_create_child : bool; 144 may_rename : bool; 145 may_delete : bool; 146 may_submit : bool; 147 } 148 149 (** Filter condition for mailbox queries. See RFC8621 Section 2.3. *) 150 type mailbox_filter_condition = { 151 parent_id : id option; 152 name : string option; 153 role : string option; 154 has_any_role : bool option; 155 is_subscribed : bool option; 156 } 157 158 type mailbox_query_filter = [ 159 | `And of mailbox_query_filter list 160 | `Or of mailbox_query_filter list 161 | `Not of mailbox_query_filter 162 | `Condition of mailbox_filter_condition 163 ] 164 165 (** Mailbox/get request arguments. See RFC8621 Section 2.1. *) 166 type mailbox_get_arguments = { 167 account_id : id; 168 ids : id list option; 169 properties : string list option; 170 } 171 172 (** Mailbox/get response. See RFC8621 Section 2.1. *) 173 type mailbox_get_response = { 174 account_id : id; 175 state : string; 176 list : mailbox list; 177 not_found : id list; 178 } 179 180 (** Mailbox/changes request arguments. See RFC8621 Section 2.2. *) 181 type mailbox_changes_arguments = { 182 account_id : id; 183 since_state : string; 184 max_changes : unsigned_int option; 185 } 186 187 (** Mailbox/changes response. See RFC8621 Section 2.2. *) 188 type mailbox_changes_response = { 189 account_id : id; 190 old_state : string; 191 new_state : string; 192 has_more_changes : bool; 193 created : id list; 194 updated : id list; 195 destroyed : id list; 196 } 197 198 (** Mailbox/query request arguments. See RFC8621 Section 2.3. *) 199 type mailbox_query_arguments = { 200 account_id : id; 201 filter : mailbox_query_filter option; 202 sort : [ `name | `role | `sort_order ] list option; 203 limit : unsigned_int option; 204 } 205 206 (** Mailbox/query response. See RFC8621 Section 2.3. *) 207 type mailbox_query_response = { 208 account_id : id; 209 query_state : string; 210 can_calculate_changes : bool; 211 position : unsigned_int; 212 ids : id list; 213 total : unsigned_int option; 214 } 215 216 (** Mailbox/queryChanges request arguments. See RFC8621 Section 2.4. *) 217 type mailbox_query_changes_arguments = { 218 account_id : id; 219 filter : mailbox_query_filter option; 220 sort : [ `name | `role | `sort_order ] list option; 221 since_query_state : string; 222 max_changes : unsigned_int option; 223 up_to_id : id option; 224 } 225 226 (** Mailbox/queryChanges response. See RFC8621 Section 2.4. *) 227 type mailbox_query_changes_response = { 228 account_id : id; 229 old_query_state : string; 230 new_query_state : string; 231 total : unsigned_int option; 232 removed : id list; 233 added : mailbox_query_changes_added list; 234 } 235 236 and mailbox_query_changes_added = { 237 id : id; 238 index : unsigned_int; 239 } 240 241 (** Mailbox/set request arguments. See RFC8621 Section 2.5. *) 242 type mailbox_set_arguments = { 243 account_id : id; 244 if_in_state : string option; 245 create : (id * mailbox_creation) list option; 246 update : (id * mailbox_update) list option; 247 destroy : id list option; 248 } 249 250 and mailbox_creation = { 251 name : string; 252 parent_id : id option; 253 role : string option; 254 sort_order : unsigned_int option; 255 is_subscribed : bool option; 256 } 257 258 and mailbox_update = { 259 name : string option; 260 parent_id : id option; 261 role : string option; 262 sort_order : unsigned_int option; 263 is_subscribed : bool option; 264 } 265 266 (** Mailbox/set response. See RFC8621 Section 2.5. *) 267 type mailbox_set_response = { 268 account_id : id; 269 old_state : string option; 270 new_state : string; 271 created : (id * mailbox) list option; 272 updated : id list option; 273 destroyed : id list option; 274 not_created : (id * set_error) list option; 275 not_updated : (id * set_error) list option; 276 not_destroyed : (id * set_error) list option; 277 } 278 279 (** {1:thread Thread objects} *) 280 281 (** A thread in a mail account. See RFC8621 Section 3. *) 282 type thread = { 283 id : id; 284 email_ids : id list; 285 } 286 287 (** Thread/get request arguments. See RFC8621 Section 3.1. *) 288 type thread_get_arguments = { 289 account_id : id; 290 ids : id list option; 291 properties : string list option; 292 } 293 294 (** Thread/get response. See RFC8621 Section 3.1. *) 295 type thread_get_response = { 296 account_id : id; 297 state : string; 298 list : thread list; 299 not_found : id list; 300 } 301 302 (** Thread/changes request arguments. See RFC8621 Section 3.2. *) 303 type thread_changes_arguments = { 304 account_id : id; 305 since_state : string; 306 max_changes : unsigned_int option; 307 } 308 309 (** Thread/changes response. See RFC8621 Section 3.2. *) 310 type thread_changes_response = { 311 account_id : id; 312 old_state : string; 313 new_state : string; 314 has_more_changes : bool; 315 created : id list; 316 updated : id list; 317 destroyed : id list; 318 } 319 320 (** {1:email Email objects} *) 321 322 (** Addressing (mailbox) information. See RFC8621 Section 4.1.1. *) 323 type email_address = { 324 name : string option; 325 email : string; 326 parameters : (string * string) list; 327 } 328 329 (** Message header field. See RFC8621 Section 4.1.2. *) 330 type header = { 331 name : string; 332 value : string; 333 } 334 335 (** Email keyword (flag). See RFC8621 Section 4.3. *) 336 type keyword = 337 | Flagged 338 | Answered 339 | Draft 340 | Forwarded 341 | Phishing 342 | Junk 343 | NotJunk 344 | Seen 345 | Unread 346 | Custom of string 347 348 (** Email message. See RFC8621 Section 4. *) 349 type email = { 350 id : id; 351 blob_id : id; 352 thread_id : id; 353 mailbox_ids : (id * bool) list; 354 keywords : (keyword * bool) list; 355 size : unsigned_int; 356 received_at : utc_date; 357 message_id : string list; 358 in_reply_to : string list option; 359 references : string list option; 360 sender : email_address list option; 361 from : email_address list option; 362 to_ : email_address list option; 363 cc : email_address list option; 364 bcc : email_address list option; 365 reply_to : email_address list option; 366 subject : string option; 367 sent_at : utc_date option; 368 has_attachment : bool option; 369 preview : string option; 370 body_values : (string * string) list option; 371 text_body : email_body_part list option; 372 html_body : email_body_part list option; 373 attachments : email_body_part list option; 374 headers : header list option; 375 } 376 377 (** Email body part. See RFC8621 Section 4.1.4. *) 378 and email_body_part = { 379 part_id : string option; 380 blob_id : id option; 381 size : unsigned_int option; 382 headers : header list option; 383 name : string option; 384 type_ : string option; 385 charset : string option; 386 disposition : string option; 387 cid : string option; 388 language : string list option; 389 location : string option; 390 sub_parts : email_body_part list option; 391 header_parameter_name : string option; 392 header_parameter_value : string option; 393 } 394 395 (** Email query filter condition. See RFC8621 Section 4.4. *) 396 type email_filter_condition = { 397 in_mailbox : id option; 398 in_mailbox_other_than : id list option; 399 min_size : unsigned_int option; 400 max_size : unsigned_int option; 401 before : utc_date option; 402 after : utc_date option; 403 header : (string * string) option; 404 from : string option; 405 to_ : string option; 406 cc : string option; 407 bcc : string option; 408 subject : string option; 409 body : string option; 410 has_keyword : string option; 411 not_keyword : string option; 412 has_attachment : bool option; 413 text : string option; 414 } 415 416 type email_query_filter = [ 417 | `And of email_query_filter list 418 | `Or of email_query_filter list 419 | `Not of email_query_filter 420 | `Condition of email_filter_condition 421 ] 422 423 (** Email/get request arguments. See RFC8621 Section 4.5. *) 424 type email_get_arguments = { 425 account_id : id; 426 ids : id list option; 427 properties : string list option; 428 body_properties : string list option; 429 fetch_text_body_values : bool option; 430 fetch_html_body_values : bool option; 431 fetch_all_body_values : bool option; 432 max_body_value_bytes : unsigned_int option; 433 } 434 435 (** Email/get response. See RFC8621 Section 4.5. *) 436 type email_get_response = { 437 account_id : id; 438 state : string; 439 list : email list; 440 not_found : id list; 441 } 442 443 (** Email/changes request arguments. See RFC8621 Section 4.6. *) 444 type email_changes_arguments = { 445 account_id : id; 446 since_state : string; 447 max_changes : unsigned_int option; 448 } 449 450 (** Email/changes response. See RFC8621 Section 4.6. *) 451 type email_changes_response = { 452 account_id : id; 453 old_state : string; 454 new_state : string; 455 has_more_changes : bool; 456 created : id list; 457 updated : id list; 458 destroyed : id list; 459 } 460 461 (** Email/query request arguments. See RFC8621 Section 4.4. *) 462 type email_query_arguments = { 463 account_id : id; 464 filter : email_query_filter option; 465 sort : comparator list option; 466 collapse_threads : bool option; 467 position : unsigned_int option; 468 anchor : id option; 469 anchor_offset : int_t option; 470 limit : unsigned_int option; 471 calculate_total : bool option; 472 } 473 474 (** Email/query response. See RFC8621 Section 4.4. *) 475 type email_query_response = { 476 account_id : id; 477 query_state : string; 478 can_calculate_changes : bool; 479 position : unsigned_int; 480 ids : id list; 481 total : unsigned_int option; 482 thread_ids : id list option; 483 } 484 485 (** Email/queryChanges request arguments. See RFC8621 Section 4.7. *) 486 type email_query_changes_arguments = { 487 account_id : id; 488 filter : email_query_filter option; 489 sort : comparator list option; 490 collapse_threads : bool option; 491 since_query_state : string; 492 max_changes : unsigned_int option; 493 up_to_id : id option; 494 } 495 496 (** Email/queryChanges response. See RFC8621 Section 4.7. *) 497 type email_query_changes_response = { 498 account_id : id; 499 old_query_state : string; 500 new_query_state : string; 501 total : unsigned_int option; 502 removed : id list; 503 added : email_query_changes_added list; 504 } 505 506 and email_query_changes_added = { 507 id : id; 508 index : unsigned_int; 509 } 510 511 (** Email/set request arguments. See RFC8621 Section 4.8. *) 512 type email_set_arguments = { 513 account_id : id; 514 if_in_state : string option; 515 create : (id * email_creation) list option; 516 update : (id * email_update) list option; 517 destroy : id list option; 518 } 519 520 and email_creation = { 521 mailbox_ids : (id * bool) list; 522 keywords : (keyword * bool) list option; 523 received_at : utc_date option; 524 message_id : string list option; 525 in_reply_to : string list option; 526 references : string list option; 527 sender : email_address list option; 528 from : email_address list option; 529 to_ : email_address list option; 530 cc : email_address list option; 531 bcc : email_address list option; 532 reply_to : email_address list option; 533 subject : string option; 534 body_values : (string * string) list option; 535 text_body : email_body_part list option; 536 html_body : email_body_part list option; 537 attachments : email_body_part list option; 538 headers : header list option; 539 } 540 541 and email_update = { 542 keywords : (keyword * bool) list option; 543 mailbox_ids : (id * bool) list option; 544 } 545 546 (** Email/set response. See RFC8621 Section 4.8. *) 547 type email_set_response = { 548 account_id : id; 549 old_state : string option; 550 new_state : string; 551 created : (id * email) list option; 552 updated : id list option; 553 destroyed : id list option; 554 not_created : (id * set_error) list option; 555 not_updated : (id * set_error) list option; 556 not_destroyed : (id * set_error) list option; 557 } 558 559 (** Email/copy request arguments. See RFC8621 Section 4.9. *) 560 type email_copy_arguments = { 561 from_account_id : id; 562 account_id : id; 563 create : (id * email_creation) list; 564 on_success_destroy_original : bool option; 565 } 566 567 (** Email/copy response. See RFC8621 Section 4.9. *) 568 type email_copy_response = { 569 from_account_id : id; 570 account_id : id; 571 created : (id * email) list option; 572 not_created : (id * set_error) list option; 573 } 574 575 (** Email/import request arguments. See RFC8621 Section 4.10. *) 576 type email_import_arguments = { 577 account_id : id; 578 emails : (id * email_import) list; 579 } 580 581 and email_import = { 582 blob_id : id; 583 mailbox_ids : (id * bool) list; 584 keywords : (keyword * bool) list option; 585 received_at : utc_date option; 586 } 587 588 (** Email/import response. See RFC8621 Section 4.10. *) 589 type email_import_response = { 590 account_id : id; 591 created : (id * email) list option; 592 not_created : (id * set_error) list option; 593 } 594 595 (** {1:search_snippet Search snippets} *) 596 597 (** SearchSnippet/get request arguments. See RFC8621 Section 4.11. *) 598 type search_snippet_get_arguments = { 599 account_id : id; 600 email_ids : id list; 601 filter : email_filter_condition; 602 } 603 604 (** SearchSnippet/get response. See RFC8621 Section 4.11. *) 605 type search_snippet_get_response = { 606 account_id : id; 607 list : (id * search_snippet) list; 608 not_found : id list; 609 } 610 611 and search_snippet = { 612 subject : string option; 613 preview : string option; 614 } 615 616 (** {1:submission EmailSubmission objects} *) 617 618 (** EmailSubmission address. See RFC8621 Section 5.1. *) 619 type submission_address = { 620 email : string; 621 parameters : (string * string) list option; 622 } 623 624 (** Email submission object. See RFC8621 Section 5.1. *) 625 type email_submission = { 626 id : id; 627 identity_id : id; 628 email_id : id; 629 thread_id : id; 630 envelope : envelope option; 631 send_at : utc_date option; 632 undo_status : [ 633 | `pending 634 | `final 635 | `canceled 636 ] option; 637 delivery_status : (string * submission_status) list option; 638 dsn_blob_ids : (string * id) list option; 639 mdn_blob_ids : (string * id) list option; 640 } 641 642 (** Envelope for mail submission. See RFC8621 Section 5.1. *) 643 and envelope = { 644 mail_from : submission_address; 645 rcpt_to : submission_address list; 646 } 647 648 (** Delivery status for submitted email. See RFC8621 Section 5.1. *) 649 and submission_status = { 650 smtp_reply : string; 651 delivered : string option; 652 } 653 654 (** EmailSubmission/get request arguments. See RFC8621 Section 5.3. *) 655 type email_submission_get_arguments = { 656 account_id : id; 657 ids : id list option; 658 properties : string list option; 659 } 660 661 (** EmailSubmission/get response. See RFC8621 Section 5.3. *) 662 type email_submission_get_response = { 663 account_id : id; 664 state : string; 665 list : email_submission list; 666 not_found : id list; 667 } 668 669 (** EmailSubmission/changes request arguments. See RFC8621 Section 5.4. *) 670 type email_submission_changes_arguments = { 671 account_id : id; 672 since_state : string; 673 max_changes : unsigned_int option; 674 } 675 676 (** EmailSubmission/changes response. See RFC8621 Section 5.4. *) 677 type email_submission_changes_response = { 678 account_id : id; 679 old_state : string; 680 new_state : string; 681 has_more_changes : bool; 682 created : id list; 683 updated : id list; 684 destroyed : id list; 685 } 686 687 (** EmailSubmission/query filter condition. See RFC8621 Section 5.5. *) 688 type email_submission_filter_condition = { 689 identity_id : id option; 690 email_id : id option; 691 thread_id : id option; 692 before : utc_date option; 693 after : utc_date option; 694 subject : string option; 695 } 696 697 type email_submission_query_filter = [ 698 | `And of email_submission_query_filter list 699 | `Or of email_submission_query_filter list 700 | `Not of email_submission_query_filter 701 | `Condition of email_submission_filter_condition 702 ] 703 704 (** EmailSubmission/query request arguments. See RFC8621 Section 5.5. *) 705 type email_submission_query_arguments = { 706 account_id : id; 707 filter : email_submission_query_filter option; 708 sort : comparator list option; 709 position : unsigned_int option; 710 anchor : id option; 711 anchor_offset : int_t option; 712 limit : unsigned_int option; 713 calculate_total : bool option; 714 } 715 716 (** EmailSubmission/query response. See RFC8621 Section 5.5. *) 717 type email_submission_query_response = { 718 account_id : id; 719 query_state : string; 720 can_calculate_changes : bool; 721 position : unsigned_int; 722 ids : id list; 723 total : unsigned_int option; 724 } 725 726 (** EmailSubmission/set request arguments. See RFC8621 Section 5.6. *) 727 type email_submission_set_arguments = { 728 account_id : id; 729 if_in_state : string option; 730 create : (id * email_submission_creation) list option; 731 update : (id * email_submission_update) list option; 732 destroy : id list option; 733 on_success_update_email : (id * email_update) list option; 734 } 735 736 and email_submission_creation = { 737 email_id : id; 738 identity_id : id; 739 envelope : envelope option; 740 send_at : utc_date option; 741 } 742 743 and email_submission_update = { 744 email_id : id option; 745 identity_id : id option; 746 envelope : envelope option; 747 undo_status : [`canceled] option; 748 } 749 750 (** EmailSubmission/set response. See RFC8621 Section 5.6. *) 751 type email_submission_set_response = { 752 account_id : id; 753 old_state : string option; 754 new_state : string; 755 created : (id * email_submission) list option; 756 updated : id list option; 757 destroyed : id list option; 758 not_created : (id * set_error) list option; 759 not_updated : (id * set_error) list option; 760 not_destroyed : (id * set_error) list option; 761 } 762 763 (** {1:identity Identity objects} *) 764 765 (** Identity for sending mail. See RFC8621 Section 6. *) 766 type identity = { 767 id : id; 768 name : string; 769 email : string; 770 reply_to : email_address list option; 771 bcc : email_address list option; 772 text_signature : string option; 773 html_signature : string option; 774 may_delete : bool; 775 } 776 777 (** Identity/get request arguments. See RFC8621 Section 6.1. *) 778 type identity_get_arguments = { 779 account_id : id; 780 ids : id list option; 781 properties : string list option; 782 } 783 784 (** Identity/get response. See RFC8621 Section 6.1. *) 785 type identity_get_response = { 786 account_id : id; 787 state : string; 788 list : identity list; 789 not_found : id list; 790 } 791 792 (** Identity/changes request arguments. See RFC8621 Section 6.2. *) 793 type identity_changes_arguments = { 794 account_id : id; 795 since_state : string; 796 max_changes : unsigned_int option; 797 } 798 799 (** Identity/changes response. See RFC8621 Section 6.2. *) 800 type identity_changes_response = { 801 account_id : id; 802 old_state : string; 803 new_state : string; 804 has_more_changes : bool; 805 created : id list; 806 updated : id list; 807 destroyed : id list; 808 } 809 810 (** Identity/set request arguments. See RFC8621 Section 6.3. *) 811 type identity_set_arguments = { 812 account_id : id; 813 if_in_state : string option; 814 create : (id * identity_creation) list option; 815 update : (id * identity_update) list option; 816 destroy : id list option; 817 } 818 819 and identity_creation = { 820 name : string; 821 email : string; 822 reply_to : email_address list option; 823 bcc : email_address list option; 824 text_signature : string option; 825 html_signature : string option; 826 } 827 828 and identity_update = { 829 name : string option; 830 email : string option; 831 reply_to : email_address list option; 832 bcc : email_address list option; 833 text_signature : string option; 834 html_signature : string option; 835 } 836 837 (** Identity/set response. See RFC8621 Section 6.3. *) 838 type identity_set_response = { 839 account_id : id; 840 old_state : string option; 841 new_state : string; 842 created : (id * identity) list option; 843 updated : id list option; 844 destroyed : id list option; 845 not_created : (id * set_error) list option; 846 not_updated : (id * set_error) list option; 847 not_destroyed : (id * set_error) list option; 848 } 849 850 (** {1:vacation_response VacationResponse objects} *) 851 852 (** Vacation auto-reply setting. See RFC8621 Section 7. *) 853 type vacation_response = { 854 id : id; 855 is_enabled : bool; 856 from_date : utc_date option; 857 to_date : utc_date option; 858 subject : string option; 859 text_body : string option; 860 html_body : string option; 861 } 862 863 (** VacationResponse/get request arguments. See RFC8621 Section 7.2. *) 864 type vacation_response_get_arguments = { 865 account_id : id; 866 ids : id list option; 867 properties : string list option; 868 } 869 870 (** VacationResponse/get response. See RFC8621 Section 7.2. *) 871 type vacation_response_get_response = { 872 account_id : id; 873 state : string; 874 list : vacation_response list; 875 not_found : id list; 876 } 877 878 (** VacationResponse/set request arguments. See RFC8621 Section 7.3. *) 879 type vacation_response_set_arguments = { 880 account_id : id; 881 if_in_state : string option; 882 update : (id * vacation_response_update) list; 883 } 884 885 and vacation_response_update = { 886 is_enabled : bool option; 887 from_date : utc_date option; 888 to_date : utc_date option; 889 subject : string option; 890 text_body : string option; 891 html_body : string option; 892 } 893 894 (** VacationResponse/set response. See RFC8621 Section 7.3. *) 895 type vacation_response_set_response = { 896 account_id : id; 897 old_state : string option; 898 new_state : string; 899 updated : id list option; 900 not_updated : (id * set_error) list option; 901 } 902end 903 904(** {1 JSON serialization} *) 905 906module Json = struct 907 open Types 908 909 (** {2 Helper functions for serialization} *) 910 911 let string_of_mailbox_role = function 912 | All -> "all" 913 | Archive -> "archive" 914 | Drafts -> "drafts" 915 | Flagged -> "flagged" 916 | Important -> "important" 917 | Inbox -> "inbox" 918 | Junk -> "junk" 919 | Sent -> "sent" 920 | Trash -> "trash" 921 | Unknown s -> s 922 923 let mailbox_role_of_string = function 924 | "all" -> All 925 | "archive" -> Archive 926 | "drafts" -> Drafts 927 | "flagged" -> Flagged 928 | "important" -> Important 929 | "inbox" -> Inbox 930 | "junk" -> Junk 931 | "sent" -> Sent 932 | "trash" -> Trash 933 | s -> Unknown s 934 935 let string_of_keyword = function 936 | Flagged -> "$flagged" 937 | Answered -> "$answered" 938 | Draft -> "$draft" 939 | Forwarded -> "$forwarded" 940 | Phishing -> "$phishing" 941 | Junk -> "$junk" 942 | NotJunk -> "$notjunk" 943 | Seen -> "$seen" 944 | Unread -> "$unread" 945 | Custom s -> s 946 947 let keyword_of_string = function 948 | "$flagged" -> Flagged 949 | "$answered" -> Answered 950 | "$draft" -> Draft 951 | "$forwarded" -> Forwarded 952 | "$phishing" -> Phishing 953 | "$junk" -> Junk 954 | "$notjunk" -> NotJunk 955 | "$seen" -> Seen 956 | "$unread" -> Unread 957 | s -> Custom s 958 959 (** {2 Mailbox serialization} *) 960 961 (** TODO:claude - Need to implement all JSON serialization functions 962 for each type we've defined. This would be a substantial amount of 963 code and likely require additional understanding of the ezjsonm API. 964 965 For a full implementation, we would need functions to convert between 966 OCaml types and JSON for each of: 967 - mailbox, mailbox_rights, mailbox query/update operations 968 - thread operations 969 - email, email_address, header, email_body_part 970 - email query/update operations 971 - submission operations 972 - identity operations 973 - vacation response operations 974 *) 975end 976 977(** {1 API functions} *) 978 979open Lwt.Syntax 980open Jmap.Api 981open Jmap.Types 982 983(** Authentication credentials for a JMAP server *) 984type credentials = { 985 username: string; 986 password: string; 987} 988 989(** Connection to a JMAP mail server *) 990type connection = { 991 session: Jmap.Types.session; 992 config: Jmap.Api.config; 993} 994 995(** Convert JSON mail object to OCaml type *) 996let mailbox_of_json json = 997 try 998 let open Ezjsonm in 999 Printf.printf "Parsing mailbox JSON\n"; 1000 1001 let id = get_string (find json ["id"]) in 1002 Printf.printf "Got id: %s\n" id; 1003 1004 let name = get_string (find json ["name"]) in 1005 Printf.printf "Got name: %s\n" name; 1006 1007 (* Handle parentId which can be null *) 1008 let parent_id = 1009 match find_opt json ["parentId"] with 1010 | Some (`Null) -> None 1011 | Some (`String s) -> Some s 1012 | None -> None 1013 | _ -> None 1014 in 1015 Printf.printf "Got parent_id: %s\n" (match parent_id with Some p -> p | None -> "None"); 1016 1017 (* Handle role which might be null *) 1018 let role = 1019 match find_opt json ["role"] with 1020 | Some (`Null) -> None 1021 | Some (`String s) -> Some (Json.mailbox_role_of_string s) 1022 | None -> None 1023 | _ -> None 1024 in 1025 Printf.printf "Got role\n"; 1026 1027 let sort_order = get_int (find json ["sortOrder"]) in 1028 Printf.printf "Got sort_order: %d\n" sort_order; 1029 1030 let total_emails = get_int (find json ["totalEmails"]) in 1031 Printf.printf "Got total_emails: %d\n" total_emails; 1032 1033 let unread_emails = get_int (find json ["unreadEmails"]) in 1034 Printf.printf "Got unread_emails: %d\n" unread_emails; 1035 1036 let total_threads = get_int (find json ["totalThreads"]) in 1037 Printf.printf "Got total_threads: %d\n" total_threads; 1038 1039 let unread_threads = get_int (find json ["unreadThreads"]) in 1040 Printf.printf "Got unread_threads: %d\n" unread_threads; 1041 1042 let is_subscribed = get_bool (find json ["isSubscribed"]) in 1043 Printf.printf "Got is_subscribed: %b\n" is_subscribed; 1044 1045 let rights_json = find json ["myRights"] in 1046 Printf.printf "Got rights_json\n"; 1047 1048 let my_rights = { 1049 Types.may_read_items = get_bool (find rights_json ["mayReadItems"]); 1050 may_add_items = get_bool (find rights_json ["mayAddItems"]); 1051 may_remove_items = get_bool (find rights_json ["mayRemoveItems"]); 1052 may_set_seen = get_bool (find rights_json ["maySetSeen"]); 1053 may_set_keywords = get_bool (find rights_json ["maySetKeywords"]); 1054 may_create_child = get_bool (find rights_json ["mayCreateChild"]); 1055 may_rename = get_bool (find rights_json ["mayRename"]); 1056 may_delete = get_bool (find rights_json ["mayDelete"]); 1057 may_submit = get_bool (find rights_json ["maySubmit"]); 1058 } in 1059 Printf.printf "Constructed my_rights\n"; 1060 1061 let result = { 1062 Types.id; 1063 name; 1064 parent_id; 1065 role; 1066 sort_order; 1067 total_emails; 1068 unread_emails; 1069 total_threads; 1070 unread_threads; 1071 is_subscribed; 1072 my_rights; 1073 } in 1074 Printf.printf "Constructed mailbox result\n"; 1075 1076 Ok (result) 1077 with 1078 | Not_found as e -> 1079 Printf.printf "Not_found error: %s\n" (Printexc.to_string e); 1080 Printexc.print_backtrace stdout; 1081 Error (Parse_error "Required field not found in mailbox object") 1082 | Invalid_argument msg -> 1083 Printf.printf "Invalid_argument error: %s\n" msg; 1084 Error (Parse_error msg) 1085 | e -> 1086 Printf.printf "Unknown error: %s\n" (Printexc.to_string e); 1087 Error (Parse_error (Printexc.to_string e)) 1088 1089(** Convert JSON email object to OCaml type *) 1090let email_of_json json = 1091 try 1092 let open Ezjsonm in 1093 Printf.printf "Parsing email JSON\n"; 1094 1095 let id = get_string (find json ["id"]) in 1096 Printf.printf "Got email id: %s\n" id; 1097 1098 let blob_id = get_string (find json ["blobId"]) in 1099 let thread_id = get_string (find json ["threadId"]) in 1100 1101 (* Process mailboxIds map *) 1102 let mailbox_ids_json = find json ["mailboxIds"] in 1103 let mailbox_ids = match mailbox_ids_json with 1104 | `O items -> List.map (fun (id, v) -> (id, get_bool v)) items 1105 | _ -> raise (Invalid_argument "mailboxIds is not an object") 1106 in 1107 1108 (* Process keywords map *) 1109 let keywords_json = find json ["keywords"] in 1110 let keywords = match keywords_json with 1111 | `O items -> List.map (fun (k, v) -> 1112 (Json.keyword_of_string k, get_bool v)) items 1113 | _ -> raise (Invalid_argument "keywords is not an object") 1114 in 1115 1116 let size = get_int (find json ["size"]) in 1117 let received_at = get_string (find json ["receivedAt"]) in 1118 1119 (* Handle messageId which might be an array or missing *) 1120 let message_id = 1121 match find_opt json ["messageId"] with 1122 | Some (`A ids) -> List.map (fun id -> 1123 match id with 1124 | `String s -> s 1125 | _ -> raise (Invalid_argument "messageId item is not a string") 1126 ) ids 1127 | Some (`String s) -> [s] (* Handle single string case *) 1128 | None -> [] (* Handle missing case *) 1129 | _ -> raise (Invalid_argument "messageId has unexpected type") 1130 in 1131 1132 (* Parse optional fields *) 1133 let parse_email_addresses opt_json = 1134 match opt_json with 1135 | Some (`A items) -> 1136 Some (List.map (fun addr_json -> 1137 let name = 1138 match find_opt addr_json ["name"] with 1139 | Some (`String s) -> Some s 1140 | Some (`Null) -> None 1141 | None -> None 1142 | _ -> None 1143 in 1144 let email = get_string (find addr_json ["email"]) in 1145 let parameters = 1146 match find_opt addr_json ["parameters"] with 1147 | Some (`O items) -> List.map (fun (k, v) -> 1148 match v with 1149 | `String s -> (k, s) 1150 | _ -> (k, "") 1151 ) items 1152 | _ -> [] 1153 in 1154 { Types.name; email; parameters } 1155 ) items) 1156 | _ -> None 1157 in 1158 1159 (* Handle optional string arrays with null handling *) 1160 let parse_string_array_opt field_name = 1161 match find_opt json [field_name] with 1162 | Some (`A ids) -> 1163 Some (List.filter_map (function 1164 | `String s -> Some s 1165 | _ -> None 1166 ) ids) 1167 | Some (`Null) -> None 1168 | None -> None 1169 | _ -> None 1170 in 1171 1172 let in_reply_to = parse_string_array_opt "inReplyTo" in 1173 let references = parse_string_array_opt "references" in 1174 1175 let sender = parse_email_addresses (find_opt json ["sender"]) in 1176 let from = parse_email_addresses (find_opt json ["from"]) in 1177 let to_ = parse_email_addresses (find_opt json ["to"]) in 1178 let cc = parse_email_addresses (find_opt json ["cc"]) in 1179 let bcc = parse_email_addresses (find_opt json ["bcc"]) in 1180 let reply_to = parse_email_addresses (find_opt json ["replyTo"]) in 1181 1182 (* Handle optional string fields with null handling *) 1183 let parse_string_opt field_name = 1184 match find_opt json [field_name] with 1185 | Some (`String s) -> Some s 1186 | Some (`Null) -> None 1187 | None -> None 1188 | _ -> None 1189 in 1190 1191 let subject = parse_string_opt "subject" in 1192 let sent_at = parse_string_opt "sentAt" in 1193 1194 (* Handle optional boolean fields with null handling *) 1195 let parse_bool_opt field_name = 1196 match find_opt json [field_name] with 1197 | Some (`Bool b) -> Some b 1198 | Some (`Null) -> None 1199 | None -> None 1200 | _ -> None 1201 in 1202 1203 let has_attachment = parse_bool_opt "hasAttachment" in 1204 let preview = parse_string_opt "preview" in 1205 1206 (* Body parts parsing would go here - omitting for brevity *) 1207 Printf.printf "Email parsed successfully\n"; 1208 1209 Ok ({ 1210 Types.id; 1211 blob_id; 1212 thread_id; 1213 mailbox_ids; 1214 keywords; 1215 size; 1216 received_at; 1217 message_id; 1218 in_reply_to; 1219 references; 1220 sender; 1221 from; 1222 to_; 1223 cc; 1224 bcc; 1225 reply_to; 1226 subject; 1227 sent_at; 1228 has_attachment; 1229 preview; 1230 body_values = None; 1231 text_body = None; 1232 html_body = None; 1233 attachments = None; 1234 headers = None; 1235 }) 1236 with 1237 | Not_found as e -> 1238 Printf.printf "Email parse error - Not_found: %s\n" (Printexc.to_string e); 1239 Printexc.print_backtrace stdout; 1240 Error (Parse_error "Required field not found in email object") 1241 | Invalid_argument msg -> 1242 Printf.printf "Email parse error - Invalid_argument: %s\n" msg; 1243 Error (Parse_error msg) 1244 | e -> 1245 Printf.printf "Email parse error - Unknown: %s\n" (Printexc.to_string e); 1246 Error (Parse_error (Printexc.to_string e)) 1247 1248(** Login to a JMAP server and establish a connection 1249 @param uri The URI of the JMAP server 1250 @param credentials Authentication credentials 1251 @return A connection object if successful 1252 1253 TODO:claude *) 1254let login ~uri ~credentials = 1255 let* session_result = get_session (Uri.of_string uri) 1256 ~username:credentials.username 1257 ~authentication_token:credentials.password 1258 () in 1259 match session_result with 1260 | Ok session -> 1261 let api_uri = Uri.of_string session.api_url in 1262 let config = { 1263 api_uri; 1264 username = credentials.username; 1265 authentication_token = credentials.password; 1266 } in 1267 Lwt.return (Ok { session; config }) 1268 | Error e -> Lwt.return (Error e) 1269 1270(** Login to a JMAP server using an API token 1271 @param uri The URI of the JMAP server 1272 @param api_token The API token for authentication 1273 @return A connection object if successful 1274 1275 TODO:claude *) 1276let login_with_token ~uri ~api_token = 1277 let* session_result = get_session (Uri.of_string uri) 1278 ~api_token 1279 () in 1280 match session_result with 1281 | Ok session -> 1282 let api_uri = Uri.of_string session.api_url in 1283 let config = { 1284 api_uri; 1285 username = ""; (* Empty username indicates we're using token auth *) 1286 authentication_token = api_token; 1287 } in 1288 Lwt.return (Ok { session; config }) 1289 | Error e -> Lwt.return (Error e) 1290 1291(** Get all mailboxes for an account 1292 @param conn The JMAP connection 1293 @param account_id The account ID to get mailboxes for 1294 @return A list of mailboxes if successful 1295 1296 TODO:claude *) 1297let get_mailboxes conn ~account_id = 1298 let request = { 1299 using = [ 1300 Jmap.Capability.to_string (Jmap.Capability.Core Core); 1301 Capability.to_string (Capability.Mail Mail) 1302 ]; 1303 method_calls = [ 1304 { 1305 name = "Mailbox/get"; 1306 arguments = `O [ 1307 ("accountId", `String account_id); 1308 ]; 1309 method_call_id = "m1"; 1310 } 1311 ]; 1312 created_ids = None; 1313 } in 1314 1315 let* response_result = make_request conn.config request in 1316 match response_result with 1317 | Ok response -> 1318 let result = 1319 try 1320 let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1321 inv.name = "Mailbox/get") response.method_responses in 1322 let args = method_response.arguments in 1323 match Ezjsonm.find_opt args ["list"] with 1324 | Some (`A mailbox_list) -> 1325 let parse_results = List.map mailbox_of_json mailbox_list in 1326 let (successes, failures) = List.partition Result.is_ok parse_results in 1327 if List.length failures > 0 then 1328 Error (Parse_error "Failed to parse some mailboxes") 1329 else 1330 Ok (List.map Result.get_ok successes) 1331 | _ -> Error (Parse_error "Mailbox list not found in response") 1332 with 1333 | Not_found -> Error (Parse_error "Mailbox/get method response not found") 1334 | e -> Error (Parse_error (Printexc.to_string e)) 1335 in 1336 Lwt.return result 1337 | Error e -> Lwt.return (Error e) 1338 1339(** Get a specific mailbox by ID 1340 @param conn The JMAP connection 1341 @param account_id The account ID 1342 @param mailbox_id The mailbox ID to retrieve 1343 @return The mailbox if found 1344 1345 TODO:claude *) 1346let get_mailbox conn ~account_id ~mailbox_id = 1347 let request = { 1348 using = [ 1349 Jmap.Capability.to_string (Jmap.Capability.Core Core); 1350 Capability.to_string (Capability.Mail Mail) 1351 ]; 1352 method_calls = [ 1353 { 1354 name = "Mailbox/get"; 1355 arguments = `O [ 1356 ("accountId", `String account_id); 1357 ("ids", `A [`String mailbox_id]); 1358 ]; 1359 method_call_id = "m1"; 1360 } 1361 ]; 1362 created_ids = None; 1363 } in 1364 1365 let* response_result = make_request conn.config request in 1366 match response_result with 1367 | Ok response -> 1368 let result = 1369 try 1370 let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1371 inv.name = "Mailbox/get") response.method_responses in 1372 let args = method_response.arguments in 1373 match Ezjsonm.find_opt args ["list"] with 1374 | Some (`A [mailbox]) -> mailbox_of_json mailbox 1375 | Some (`A []) -> Error (Parse_error ("Mailbox not found: " ^ mailbox_id)) 1376 | _ -> Error (Parse_error "Expected single mailbox in response") 1377 with 1378 | Not_found -> Error (Parse_error "Mailbox/get method response not found") 1379 | e -> Error (Parse_error (Printexc.to_string e)) 1380 in 1381 Lwt.return result 1382 | Error e -> Lwt.return (Error e) 1383 1384(** Get messages in a mailbox 1385 @param conn The JMAP connection 1386 @param account_id The account ID 1387 @param mailbox_id The mailbox ID to get messages from 1388 @param limit Optional limit on number of messages to return 1389 @return The list of email messages if successful 1390 1391 TODO:claude *) 1392let get_messages_in_mailbox conn ~account_id ~mailbox_id ?limit () = 1393 (* First query the emails in the mailbox *) 1394 let query_request = { 1395 using = [ 1396 Jmap.Capability.to_string (Jmap.Capability.Core Core); 1397 Capability.to_string (Capability.Mail Mail) 1398 ]; 1399 method_calls = [ 1400 { 1401 name = "Email/query"; 1402 arguments = `O ([ 1403 ("accountId", `String account_id); 1404 ("filter", `O [("inMailbox", `String mailbox_id)]); 1405 ("sort", `A [`O [("property", `String "receivedAt"); ("isAscending", `Bool false)]]); 1406 ] @ (match limit with 1407 | Some l -> [("limit", `Float (float_of_int l))] 1408 | None -> [] 1409 )); 1410 method_call_id = "q1"; 1411 } 1412 ]; 1413 created_ids = None; 1414 } in 1415 1416 let* query_result = make_request conn.config query_request in 1417 match query_result with 1418 | Ok query_response -> 1419 (try 1420 let query_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1421 inv.name = "Email/query") query_response.method_responses in 1422 let args = query_method.arguments in 1423 match Ezjsonm.find_opt args ["ids"] with 1424 | Some (`A ids) -> 1425 let email_ids = List.map (function 1426 | `String id -> id 1427 | _ -> raise (Invalid_argument "Email ID is not a string") 1428 ) ids in 1429 1430 (* If we have IDs, fetch the actual email objects *) 1431 if List.length email_ids > 0 then 1432 let get_request = { 1433 using = [ 1434 Jmap.Capability.to_string (Jmap.Capability.Core Core); 1435 Capability.to_string (Capability.Mail Mail) 1436 ]; 1437 method_calls = [ 1438 { 1439 name = "Email/get"; 1440 arguments = `O [ 1441 ("accountId", `String account_id); 1442 ("ids", `A (List.map (fun id -> `String id) email_ids)); 1443 ]; 1444 method_call_id = "g1"; 1445 } 1446 ]; 1447 created_ids = None; 1448 } in 1449 1450 let* get_result = make_request conn.config get_request in 1451 match get_result with 1452 | Ok get_response -> 1453 (try 1454 let get_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1455 inv.name = "Email/get") get_response.method_responses in 1456 let args = get_method.arguments in 1457 match Ezjsonm.find_opt args ["list"] with 1458 | Some (`A email_list) -> 1459 let parse_results = List.map email_of_json email_list in 1460 let (successes, failures) = List.partition Result.is_ok parse_results in 1461 if List.length failures > 0 then 1462 Lwt.return (Error (Parse_error "Failed to parse some emails")) 1463 else 1464 Lwt.return (Ok (List.map Result.get_ok successes)) 1465 | _ -> Lwt.return (Error (Parse_error "Email list not found in response")) 1466 with 1467 | Not_found -> Lwt.return (Error (Parse_error "Email/get method response not found")) 1468 | e -> Lwt.return (Error (Parse_error (Printexc.to_string e)))) 1469 | Error e -> Lwt.return (Error e) 1470 else 1471 (* No emails in mailbox *) 1472 Lwt.return (Ok []) 1473 1474 | _ -> Lwt.return (Error (Parse_error "Email IDs not found in query response")) 1475 with 1476 | Not_found -> Lwt.return (Error (Parse_error "Email/query method response not found")) 1477 | Invalid_argument msg -> Lwt.return (Error (Parse_error msg)) 1478 | e -> Lwt.return (Error (Parse_error (Printexc.to_string e)))) 1479 | Error e -> Lwt.return (Error e) 1480 1481(** Get a single email message by ID 1482 @param conn The JMAP connection 1483 @param account_id The account ID 1484 @param email_id The email ID to retrieve 1485 @return The email message if found 1486 1487 TODO:claude *) 1488let get_email conn ~account_id ~email_id = 1489 let request = { 1490 using = [ 1491 Jmap.Capability.to_string (Jmap.Capability.Core Core); 1492 Capability.to_string (Capability.Mail Mail) 1493 ]; 1494 method_calls = [ 1495 { 1496 name = "Email/get"; 1497 arguments = `O [ 1498 ("accountId", `String account_id); 1499 ("ids", `A [`String email_id]); 1500 ]; 1501 method_call_id = "m1"; 1502 } 1503 ]; 1504 created_ids = None; 1505 } in 1506 1507 let* response_result = make_request conn.config request in 1508 match response_result with 1509 | Ok response -> 1510 let result = 1511 try 1512 let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1513 inv.name = "Email/get") response.method_responses in 1514 let args = method_response.arguments in 1515 match Ezjsonm.find_opt args ["list"] with 1516 | Some (`A [email]) -> email_of_json email 1517 | Some (`A []) -> Error (Parse_error ("Email not found: " ^ email_id)) 1518 | _ -> Error (Parse_error "Expected single email in response") 1519 with 1520 | Not_found -> Error (Parse_error "Email/get method response not found") 1521 | e -> Error (Parse_error (Printexc.to_string e)) 1522 in 1523 Lwt.return result 1524 | Error e -> Lwt.return (Error e)