Project

mailschema

0.0
The project is in a healthy, maintained state
Parse, canonicalize, digest and validate Mail Action Protocol 0.3 descriptions, type contracts and implementation records, extract and build the MAP part of an email, and decide whether a verified DKIM signature qualifies a message.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 2.8
 Project Readme

mailschema for Ruby

Mail Action Protocol (MAP) 0.3 processing in Ruby. The gem reads MAP JSON strictly from its original bytes, computes RFC 8785 digests, and checks descriptions, type contracts and implementation records with the same rules and messages as the reference implementation. It also finds the one MAP part of a received email, builds that part for a message you send, and decides whether a verified DKIM signature qualifies a message for MAP actions.

It does not verify DKIM, query DNS, fetch anything an email names, grant authority or send email. Those belong to your verifier, your trusted service binding and your host policy.

gem install mailschema -v 0.3.0

Ruby 3.3 or later. The gem depends on json_schemer and mail.

Example

Parse a description, load the exact contract you resolved from your catalogue, and check the description against it:

require "mailschema"

description = Mailschema.parse_description(json)
contract = Mailschema::Contract.parse(File.binread("publication-approval-0.1.json"))
contract.digest                          # => "sha-256:de91a49d..."
contract.description_errors(description) # => []

A document that fails raises Mailschema::InvalidDocument, whose errors lists every reason as a JSON Pointer and a message, such as "/expiresAt: must be later than issuedAt".

From a received email, read the one MAP part:

result = Mailschema::Message.extract(Mail.new(raw_message))
result.status      # => :valid, :invalid or :none
result.description # the parsed description when :valid

API

All under Mailschema:

  • PROFILE, CONTEXT, DESCRIPTION_MEDIA_TYPE, DESCRIPTION_MAX_BYTES, CONTRACT_MAX_BYTES, MAX_DEPTH, EFFECTS, FORMATS and CAPABILITY_KINDS.
  • parse(input, max_bytes): MAP JSON from text or bytes. It refuses invalid UTF-8, a byte order mark, duplicate member names (also after unescaping), lone surrogates, U+0000 and noncharacters, nesting deeper than 32, and numbers that are not finite tokens of at most 64 characters within the I-JSON range. It returns plain Hash, Array, String, Integer, Float, true, false and nil values; a number with no fractional part is an Integer.
  • canonicalize(value) and digest(value): the RFC 8785 form, and sha-256: with its hex SHA-256.
  • description_errors(value) and parse_description(input).
  • contract_errors(value), Contract.new(value) and Contract.parse(input). A contract has document (frozen), digest, id, version, operation(id), details_errors(details), input_errors(operation_id, input = nil) and description_errors(description).
  • implementation_errors(value) and parse_implementation(input).
  • Message.extract(mail): a Message::Result with status :none, :invalid (with a reason) or :valid (with the description). Pass a Mail::Message read from the received bytes; it never raises for untrusted input.
  • Message.part(description): the Mail::Part that carries a description, to place in a multipart/related entity after the readable content.
  • Authentication::Signature and Authentication.qualifying(raw_message:, signatures:, organizational_domain: nil): the first signature your DKIM verifier reported that qualifies the raw message, or nil. Alignment is strict unless you pass a callable that returns a domain's organizational domain.

Every *_errors method returns an array of strings, empty when the value is valid. Schemas are validated with json_schemer (JSON Schema 2020-12, ECMA-262 regular expressions) against the profile artifacts the gem bundles.

Links