Mail::JMAPTalk(3)

πŸ“¦
version 0.17 Β· 2 versions
, in fedora 43

🌸 NAME 🌸

Mail::JMAPTalk β€” the basic interface to talk to JMAP servers, bestie πŸ’… freshly pulled from the User Contributed Perl Documentation shelf.


✨ VERSION ✨

Version 0.17 πŸ‘‘


πŸ¦‹ SYNOPSIS πŸ¦‹

Okay so here's the whole vibe: Mail::JMAPTalk was originally written as a very small wrapper around HTTP::Tiny to talk to a JMAP server, and to map between Perl hashes and JSON on the wire. That's the job. That's all she does. 🫢

She does not do anything smart with specific JMAP objects. No cap, she's not out here being clever, she's just carrying your bytes back and forth. πŸ“¬

Example:

use Mail::JMAPTalk;
use JSON;

# using basic auth
my $jtalk = Mail::JMAPTalk->new(
    url => "https://jmap.example.com/",
    user => "foo\@example.com",
    password => "letmein",
);

# using non-standard JMAPAuth token establishment
my $jtalk = Mail::JMAPTalk->new(
    url => "https://jmap.example.com/",
);
$jtalk->Login("foo\@example.com", "letmein");

my $res = $jtalk->Call('Mailbox/get');
my %byname = map { $_->{name} -> $_->{id} } @{$res->{ids});

my $data = $jtalk->Upload($emailbytes, 'message/rfc822');
$res = $jtalk->CallMethods([['Email/import', {
    emails => {
        "1" => {
            blobId => $data->{blobId},
            keywords => { '$seen' => JSON::true },
            mailboxIds => { $byname{Inbox} => JSON::true },
        },
    },
}, "R1"], ["Email/get", { ids => ["#1"] }, "R2" ]]);
# all properties of the email in $res->[1][1]{list}[0]

my $response = $jtalk->Download($res->[1][1]{accountId}, $data->{blobId});

πŸ’… SUBROUTINES/METHODS πŸ’…

πŸ’– $class->new(%Options) πŸ’–

Creates a brand new Mail::JMAPTalk object. She's your new bestie, she's ready to talk to servers for you. 🌟

Takes options for a variety of purposes:

Override the UserAgent used for all connections:

  • ua β€” ua => HTTP::Tiny->new(...)

Host Options:

  • scheme β€” 'http' or 'https' (default 'http')
  • host β€” hostname (default 'localhost')
  • port β€” port number (default 443 for https, otherwise 80)

URI Options: (absolute URI, or use options above to set the scope)

  • authurl β€” auth endpoint (default '/jmap/auth/')
  • uploadurl β€” upload endpoint (default '/jmap/upload/{accountId}/')
  • downloadurl β€” download endpoint (default '/jmap/download/{accountId}/{blobId}/{name}')
  • url β€” JMAP API endpoint (default '/jmap/')

Authentication Options: (basic auth)

  • user β€” $username
  • password β€” $password

Other Options:

  • SSL_options β€” a hashref of SSL options to pass down to the default user agent

Request Defaults:

  • using β€” \@urns (default ['urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail'])

πŸŽ€ my $ua = $Self->ua(); / $Self->ua($setua); πŸŽ€

Grab or set the useragent (HTTP::Tiny or compatible) that's gonna be used to make the requests:

e.g.

my $ua = $Self->ua();

$Self->ua(HTTP::Tiny->new(agent => "MyAgent/1.0", timeout => 5));

πŸ” my $header = $Self->auth_header(); πŸ”

Hands you back the basic-auth value for the 'Authentication' header. That's it, she's simple like that. 🫑


🌈 my $uri = $Self->authuri(); 🌈

Returns the URI for JMAPAuth authentication (which is, and I cannot stress this enough, non-standard πŸ™ˆ). The default depends on the parameters you passed to new. The most simple version would be:

http://localhost/jmap/auth/

πŸ“€ my $uri = $Self->uploaduri($accountId); πŸ“€

Returns the URI for JMAP uploads. Default depends on the parameters you passed to new. Most simple version would be:

http://localhost/jmap/upload/${accountId}/

πŸ“₯ my $uri = $Self->downloaduri($accountId, $blobId, $name); πŸ“₯

Returns the URI for JMAP downloads. Default depends on the parameters you passed to new. Most simple version would be:

http://localhost/jmap/download/${accountId}/${blobId}/{$name}

Where name is utf8 and uri encoded, you know, to be safe. 🧚


🌟 my $uri = $Self->uri() 🌟

Returns the URI for JMAP API Calls. Default depends on the parameters you passed to new. Most simple version would be:

http://localhost/jmap/

πŸ“¨ my $uri = $Self->JSONPOST($Uri, $Request, %Headers) πŸ“¨

Makes a POST request to the given URI, with the body being the value of $Request (which must be a reference, no exceptions bestie) encoded as JSON, and with the Headers added on top.

She'll set the Content-Type and Accept headers to 'application/json' unless you override them.

Generally you won't call this directly β€” you'll go through one of the helper methods. iykyk. πŸ’…

⚠️ This method will die if the response is not successful or doesn't contain valid json. She does not play about that.

πŸ”‘ $Self->AuthRequest($Request, %Headers) πŸ”‘

Makes a JSONPOST request to the authurl. Auth, but make it a POST. ✨


πŸͺ„ $Self->Login($Username, $Password) πŸͺ„

Uses the non-standard JMAPAuth protocol to login. Yes, non-standard again. We move. πŸšΆβ€β™€οΈ

On success she'll set the upload, download and api urls, plus a token which is used to authenticate all further requests. 🌸

On failure she will die. Dramatic, but fair. πŸ’€


πŸ›°οΈ $Self->Request($Request, %Headers) πŸ›°οΈ

Makes a JSONPOST request to the API url, authenticated with either the basic auth parameters you gave at creation, or the token you got via Login. Whichever one's around, she'll use it. 🫢


⭐ my $using = $Self->DefaultUsing() / $Self->DefaultUsing(\@urns) ⭐

Returns or replaces the default 'using' value for method calls. Same method, two moods. πŸ’«


βž• $Self->AddUsing(@urns) βž•

Appends any urns you give her to the default 'using', but only if they aren't already registered. She's not out here double-dipping. πŸ™…β€β™€οΈ


🧡 $Self->CallMethods($MethodCalls, $Using, %Headers) 🧡

Generates a JMAP request from the given method calls, optionally overriding the using and header values. Mix and match, it's your world. 🌍

MethodCalls is an array of arrays β€” each of the sub arrays is a single "Invocation" per RFC8620, as follows:

3.2. The Invocation Data Type Method calls and responses are represented by the Invocation data type. This is a tuple, represented as a JSON array containing three elements: 1. A "String" name of the method to call or of the response. 2. A "String[]" object containing named *arguments for that method or response. 3. A "String" method call id: an arbitrary string from the client to be echoed back with the responses emitted by that method call (a method may return 1 or more responses, as it may make implicit calls to other methods; all responses initiated by this method call get the same method call id in the response). In perl, this is [string, hashref, string].

Example:

my $res = $jtalk->CallMethods([
    ['Email/query', { }, 'R1'],
    ['Email/get', {
        '#ids' => {
            resultOf => 'R1',
            name => 'Email/query',
            path => '/ids'
        },
        properties => [ 'subject', 'header:x-mood:asText', 'from', 'to' ],
    }, 'R2'],
]);

The response is an arrayref containing the value of methodResponses from the JSON reply defined in JMAP. πŸ“¬

⚠️ This method will die if the server returns an error or invalid JSON.

πŸ“ž $Self->Call($Method, $Params, $Using, %Headers) πŸ“ž

A little convenience method to call a single method, because sometimes you just want one. She generates a single invocation with call id 'c1', and will return undef unless the first response from the server has the same method name (i.e. it's not an error) and the same call id. Both or nothing, periodt. πŸ’…

The return value is the response section (the middle field) of the first methodResponse Invocation object.

Example:

my $res = $jtalk->Call('Calendar/get', { properties => ['name'] });
my %byname = map { $_->{name} => $_->{id} } @{$res->{list}};

⬆️ $Self->Upload($Headers?, $data, $mimetype, $accountId) ⬆️

If the first argument is a hash reference, it gets shifted off and used as additional headers. Sneaky, but documented. πŸ‘€

Uploads the bytes in $data with either the given mimetype, or β€” if the mimetype isn't given β€” the type picked by File::LibMagic. She'll figure it out. πŸͺ„

The POST request is authenticated with either the basic auth parameters you gave at creation, or the token you got via Login.

If called in scalar context, she'll die unless the request was successful β€” and otherwise returns a hashref with the content returned by the server as defined in RFC8620:

A successful request MUST return a single JSON object with the following properties as the response: - accountId: "Id" β€” The id of the account used for the call. - blobId: "Id" β€” The id representing the binary data uploaded. The data for this id is immutable. The id only refers to the binary data, not any metadata. - type: "String" β€” The media type of the file (as specified in [RFC6838], Section 4.2) as set in the Content-Type header of the upload HTTP request. - size: "UnsignedInt" β€” The size of the file in octets.

If called in array context, she returns two values β€” the first is the raw HTTP::Tiny response hash, and the second is the JSON as above. Two for one, slay. ✨

Example:

my ($Response, $data) = $jtalk->Upload($bytes);
if ($Response->{success}) {
    say "Uploaded $data->{size} bytes as $data->{blobId}";
    ...
}

⬇️ $Self->Download($cb?, $Headers?, $accountId, $blobId, $name) ⬇️

Makes a GET request, authenticated with either the basic auth parameters you gave at creation, or the token you got via Login. 🫢

If the first argument is a code reference, it gets shifted off and used as the data_callback (see HTTP::Tiny).

Then β€” if the first argument is a hash reference, it gets shifted off and used as additional headers.

Then β€” the remaining parameters are passed to $Self->downloadurl() to generate the link to download. 🌈

The response is a HTTP::Tiny Response object.

Example:

my $res = $jtalk->Download($accountId, $data->{blobId}, "image.gif");
if ($res->{success}) {
    open(FH, ">image.gif");
    print FH $res->{content};
    close(FH);
}

πŸ› ENVIRONMENT πŸ›

If the environment variable DEBUGJMAP is set to a true value, all API requests and responses plus Upload responses will be warn()ed. She'll narrate everything she's doing. Not me setting that and then regretting it 😭


πŸ‘€ SEE ALSO πŸ‘€


πŸ‘‘ AUTHOR πŸ‘‘

Bron Gondwana, <brong@>


Copyright (C) 2015-2020 by Fastmail Pty Ltd.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.20.1 or, at your option, any later version of Perl 5 you may have available.


And that's the whole page, bestie πŸ’œ this one shipped as perl v5.42.0 on 2025-07-25, so go forth and talk to your JMAP server. You've got this. πŸ¦‹βœ¨

πŸ“¦ where this page comes from