(** * fastmail_list - Lists emails from a Fastmail account using JMAP API * * This binary connects to the Fastmail JMAP API using an authentication token * from the JMAP_API_TOKEN environment variable and lists the most recent 100 * emails with their subjects, sender details, and labels. * * Usage: * JMAP_API_TOKEN=your_api_token ./fastmail_list [options] * * Options: * --unread List only unread messages * --labels Show labels/keywords associated with messages * --debug=LEVEL Set debug level (0-4, where 4 is most verbose) * --from=PATTERN Filter messages by sender email address * --demo-refs Demonstrate result references feature *) open Lwt.Syntax open Jmap open Jmap.Mail open Cmdliner module Mail = Jmap.Proto.Types (** Prints the email details *) let print_email ~show_labels (email : Mail.email) = let sender = match email.from with | Some (addr :: _) -> (match addr.name with | Some name -> Printf.sprintf "%s <%s>" name addr.email | None -> addr.email) | _ -> "" in let subject = match email.subject with | Some s -> s | None -> "" in let date = email.received_at in (* Format labels/keywords if requested *) let labels_str = if show_labels then let formatted = Jmap.Proto.Types.format_email_keywords email.keywords in if formatted <> "" then " [" ^ formatted ^ "]" else "" else "" in Printf.printf "%s | %s | %s%s\n" date sender subject labels_str (** Check if an email is unread *) let is_unread (email : Mail.email) = let is_unread_keyword = List.exists (fun (kw, active) -> kw = Mail.Unread && active ) email.keywords in let is_not_seen = not (List.exists (fun (kw, active) -> kw = Mail.Seen && active ) email.keywords) in is_unread_keyword || is_not_seen (** Example function demonstrating how to use higher-level library functions for JMAP requests *) let demo_result_references conn account_id = Printf.printf "\nResult Reference Demo:\n"; Printf.printf "=====================\n"; (* Step 1: Get all mailboxes *) let* mailboxes_result = Jmap.Proto.get_mailboxes conn ~account_id in match mailboxes_result with | Error err -> Printf.printf "Error getting mailboxes: %s\n" (Api.string_of_error err); Lwt.return_unit | Ok mailboxes -> (* Step 2: Get the first mailbox for this demonstration *) match mailboxes with | [] -> Printf.printf "No mailboxes found.\n"; Lwt.return_unit | first_mailbox :: _ -> Printf.printf "Using mailbox: %s\n" first_mailbox.Mail.name; (* Step 3: Get emails from the selected mailbox *) let* emails_result = Jmap.Proto.get_messages_in_mailbox conn ~account_id ~mailbox_id:first_mailbox.Mail.id ~limit:10 () in match emails_result with | Error err -> Printf.printf "Error getting emails: %s\n" (Api.string_of_error err); Lwt.return_unit | Ok emails -> Printf.printf "Successfully retrieved %d emails using the high-level library API!\n" (List.length emails); (* Display some basic information about the emails *) List.iteri (fun i (email:Jmap.Proto.Types.email) -> let subject = Option.value ~default:"" email.Mail.subject in Printf.printf " %d. %s\n" (i + 1) subject ) emails; Lwt.return_unit (** Main function for listing emails *) let list_emails unread_only show_labels debug_level demo_refs sender_filter = (* Configure logging *) init_logging ~level:debug_level ~enable_logs:(debug_level > 0) ~redact_sensitive:true (); match Sys.getenv_opt "JMAP_API_TOKEN" with | None -> Printf.eprintf "Error: JMAP_API_TOKEN environment variable not set\n"; Printf.eprintf "Usage: JMAP_API_TOKEN=your_token fastmail-list [options]\n"; exit 1 | Some token -> (* Only print token info at Info level or higher *) Logs.info (fun m -> m "Using API token: %s" (redact_token token)); (* Connect to Fastmail JMAP API *) let formatted_token = token in (* Only print instructions at Info level *) let level = match Logs.level () with | None -> 0 | Some Logs.Error -> 1 | Some Logs.Info -> 2 | Some Logs.Debug -> 3 | _ -> 2 in if level >= 2 then begin Printf.printf "\nFastmail API Instructions:\n"; Printf.printf "1. Get a token from: https://app.fastmail.com/settings/tokens\n"; Printf.printf "2. Create a new token with Mail scope (read/write)\n"; Printf.printf "3. Copy the full token (example: 3de40-5fg1h2-a1b2c3...)\n"; Printf.printf "4. Run: env JMAP_API_TOKEN=\"your_full_token\" fastmail-list [options]\n\n"; Printf.printf "Note: This example is working correctly but needs a valid Fastmail token.\n\n"; end; let* result = login_with_token ~uri:"https://api.fastmail.com/jmap/session" ~api_token:formatted_token in match result with | Error err -> Printf.eprintf "%s\n" (Api.string_of_error err); Lwt.return 1 | Ok conn -> (* Get the primary account ID *) let primary_account_id = let mail_capability = Jmap.Proto.Capability.to_string Jmap.Proto.Capability.Mail in 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 (* Run result references demo if requested *) let* () = if demo_refs then demo_result_references conn primary_account_id else Lwt.return_unit in (* Get the Inbox mailbox *) let* mailboxes_result = get_mailboxes conn ~account_id:primary_account_id in match mailboxes_result with | Error err -> Printf.eprintf "Failed to get mailboxes: %s\n" (Api.string_of_error err); Lwt.return 1 | Ok mailboxes -> (* If there's a mailbox list, just use the first one for this example *) let inbox_id = match mailboxes with | mailbox :: _ -> mailbox.Mail.id | [] -> Printf.eprintf "No mailboxes found\n"; exit 1 in (* Get messages from inbox *) let* emails_result = get_messages_in_mailbox conn ~account_id:primary_account_id ~mailbox_id:inbox_id ~limit:1000 () in match emails_result with | Error err -> Printf.eprintf "Failed to get emails: %s\n" (Api.string_of_error err); Lwt.return 1 | Ok emails -> (* Apply filters based on command line arguments *) let filtered_by_unread = if unread_only then List.filter is_unread emails else emails in (* Apply sender filter if specified *) let filtered_emails = if sender_filter <> "" then begin Printf.printf "Filtering by sender: %s\n" sender_filter; List.filter (fun email -> Jmap.Proto.email_matches_sender email sender_filter ) filtered_by_unread end else filtered_by_unread in (* Create description of applied filters *) let filter_description = let parts = [] in let parts = if unread_only then "unread" :: parts else parts in let parts = if sender_filter <> "" then ("from \"" ^ sender_filter ^ "\"") :: parts else parts in match parts with | [] -> "the most recent" | [p] -> p | _ -> String.concat " and " parts in Printf.printf "Listing %s %d emails in your inbox:\n" filter_description (List.length filtered_emails); Printf.printf "--------------------------------------------\n"; List.iter (print_email ~show_labels) filtered_emails; Lwt.return 0 (** Command line interface *) let unread_only = let doc = "List only unread messages" in Arg.(value & flag & info ["unread"] ~doc) let show_labels = let doc = "Show labels/keywords associated with messages" in Arg.(value & flag & info ["labels"] ~doc) let debug_level = let doc = "Set debug level (0-4, where 4 is most verbose)" in Arg.(value & opt int 0 & info ["debug"] ~docv:"LEVEL" ~doc) let demo_refs = let doc = "Demonstrate result references feature" in Arg.(value & flag & info ["demo-refs"] ~doc) let sender_filter = let doc = "Filter messages by sender email address (supports wildcards: * and ?)" in Arg.(value & opt string "" & info ["from"] ~docv:"PATTERN" ~doc) let cmd = let doc = "List emails from a Fastmail account using JMAP API" in let man = [ `S Manpage.s_description; `P "This program connects to the Fastmail JMAP API using an authentication token from the JMAP_API_TOKEN environment variable and lists the most recent emails with their subjects, sender details, and labels."; `P "You must obtain a Fastmail API token from https://app.fastmail.com/settings/tokens and set it in the JMAP_API_TOKEN environment variable."; `S Manpage.s_environment; `P "$(b,JMAP_API_TOKEN) The Fastmail API authentication token (required)"; `S Manpage.s_examples; `P "List all emails:"; `P " $(mname) $(i,JMAP_API_TOKEN=your_token)"; `P "List only unread emails:"; `P " $(mname) $(i,JMAP_API_TOKEN=your_token) --unread"; `P "List emails from a specific sender:"; `P " $(mname) $(i,JMAP_API_TOKEN=your_token) --from=user@example.com"; `P "List unread emails with labels:"; `P " $(mname) $(i,JMAP_API_TOKEN=your_token) --unread --labels"; ] in let info = Cmd.info "fastmail-list" ~doc ~man in Cmd.v info Term.(const (fun u l d r s -> Lwt_main.run (list_emails u l d r s) ) $ unread_only $ show_labels $ debug_level $ demo_refs $ sender_filter) (** Program entry point *) let () = exit (Cmd.eval_value cmd |> function | Ok (`Ok exit_code) -> exit_code | Ok (`Version | `Help) -> 0 | Error _ -> 1)