Noncollinear Perpendicular Microcrystalline
    Wondering what’s next for npm?Check out our public roadmap! »


    2.1.1 • Public • Published

    Command line utility and a Node.js library for email authentication.

    • SPF verification
    • DKIM signing
    • DKIM verification
    • DMARC verification
    • ARC verification
    • ARC sealing
      • Sealing on authentication
      • Sealing after modifications
    • BIMI resolving
    • MTA-STS helpers

    Pure JavaScript implementation, no external applications or compilation needed. Runs on any server/device that has Node 14+ installed.

    Command line usage

    See command line documentation for the mailauth command.

    Library Usage


    Validate DKIM signatures, SPF, DMARC, ARC and BIMI for an email.

    await authenticate(message [,options]) ->
        { dkim, spf, arc, dmarc, bimi, receivedChain, headers }


    • message is either a String, a Buffer or a Readable stream that represents an email message
    • options (object) is an optional options object
      • sender (string) is the email address from MAIL FROM command. If not set then it is parsed from the Return-Path header
      • ip (string) is the IP of remote client that sent this message
      • helo (string) is the hostname value from HELO/EHLO command
      • trustReceived (boolean) if true then parses values for ip and helo from the latest Received header if you have not set these values yourself. Defaults to false
      • mta (string) is the hostname of the server performing the authentication (defaults to os.hostname())
      • minBitLength (number) is the minimum allowed bits of RSA public keys (defaults to 1024). If a DKIM or ARC key has less bits, then validation is considered as failed
      • disableArc (boolean) if true then skip ARC checks
      • disableDmarc (boolean) if true then skip DMARC checks. This also disables checks that are dependent on DMARC (eg. BIMI)
      • disableBimi (boolean) if true then skip BIMI checks
      • seal (object) if set and message does not have a broken ARC chain, then seals the message using these values
        • signingDomain (string) ARC key domain name
        • selector (string) ARC key selector
        • privateKey (string or buffer) Private key for signing. Can be a RSA or an Ed25519 key
      • resolver (async function) is an optional async function for DNS requests. Defaults to dns.promises.resolve


    const { authenticate } = require('mailauth');
    const { dkim, spf, arc, dmarc, bimi, receivedChain, headers } = await authenticate(
        message, // either a String, a Buffer or a Readable Stream
            // SMTP transmission options if available
            ip: '', // SMTP client IP
            helo: '', // EHLO/HELO hostname
            sender: '', // MAIL FROM address
            // Uncomment if you do not want to provide ip/helo/sender manually but parse from the message
            //trustReceived: true,
            // Server processing this message, defaults to os.hostname(). Inserted into Authentication headers
            mta: '',
            //  Optional  DNS resolver function (defaults to `dns.promises.resolve`)
            resolver: async (name, rr) => await dns.promises.resolve(name, rr)
    // output authenticated message
    process.stdout.write(headers); // includes terminating line break

    Example output:

    Received-SPF: pass ( domain of designates as permitted sender) client-ip=;
     dkim=pass header.s=default header.a=rsa-sha256 header.b=TXuCNlsq;
     spf=pass ( domain of designates as permitted sender);
     arc=pass (i=2 spf=neutral dkim=pass;
    From: ...

    You can see full output (structured data for DKIM, SPF, DMARC and ARC) from this example.


    receivedChain property is an array of parsed representations of the Received: headers



    const { dkimSign } = require('mailauth/lib/dkim/sign');
    const signResult = await dkimSign(
        message, // either a String, a Buffer or a Readable Stream
            // Optional, default canonicalization, default is "relaxed/relaxed"
            canonicalization: 'relaxed/relaxed', // c=
            // Optional, default signing and hashing algorithm
            // Mostly useful when you want to use rsa-sha1, otherwise no need to set
            algorithm: 'rsa-sha256',
            // Optional, default is current time
            signTime: new Date(), // t=
            // Keys for one or more signatures
            // Different signatures can use different algorithms (mostly useful when
            // you want to sign a message both with RSA and Ed25519)
            signatureData: [
                    signingDomain: '', // d=
                    selector: 'test.rsa', // s=
                    // supported key types: RSA, Ed25519
                    privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'),
                    // Optional algorithm, default is derived from the key.
                    // Overrides whatever was set in parent object
                    algorithm: 'rsa-sha256',
                    // Optional signature specifc canonicalization, overrides whatever was set in parent object
                    canonicalization: 'relaxed/relaxed' // c=
                    // Maximum number of canonicalizated body bytes to sign (eg. the "l=" tag).
                    // Do not use though. This is available only for compatibility testing.
                    // maxBodyLength: 12345
    ); // -> {signatures: String, errors: Array} signature headers using \r\n as the line separator
    // show signing errors (if any)
    if (signResult.errors.length) {
    // output signed message
    process.stdout.write(signResult.signatures); // includes terminating line break

    Example output:

    DKIM-Signature: a=rsa-sha256; v=1; c=relaxed/relaxed;;
     s=test.rsa; b=...
    From: ...


    const { dkimVerify } = require('mailauth/lib/dkim/verify');
    // `message` is either a String, a Buffer or a Readable Stream
    const result = await dkimVerify(message);
    for (let { info } of result.results) {

    Example output:

    dkim=neutral (invalid public key) header.s=test.invalid header.b="b85yao+1"
    dkim=pass header.s=test.rsa header.b="BrEgDN4A"
    dkim=policy policy.dkim-rules=weak-key header.s=test.small header.b="d0jjgPun"



    const { spf } = require('mailauth/lib/spf');
    let result = await spf({
        sender: '',
        ip: '',
        helo: 'foo',
        mta: ''

    Example output:

    Received-SPF: pass ( domain of
     designates as permitted sender) client-ip=;



    ARC seals are automatically validated during the authentication step.

    const { authenticate } = require('mailauth');
    const { arc } = await authenticate(
        message, // either a String, a Buffer or a Readable Stream
            trustReceived: true

    Output being something like this:

      "status": {
        "result": "pass",
        "comment": "i=2 spf=neutral dkim=pass dkim=pass dmarc=fail"
      "i": 2,


    During authentication

    You can seal messages with ARC automatically in the authentication step by providing the sealing key. In this case you can not modify the message anymore as this would break the seal.

    const { authenticate } = require('mailauth');
    const { headers } = await authenticate(
        message, // either a String, a Buffer or a Readable Stream
            trustReceived: true,
            // ARC seal settings. If this is set then resulting headers include
            // a complete ARC header set (unless the message has a failing ARC chain)
            seal: {
                signingDomain: '',
                selector: 'test.rsa',
                privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem')
    // output authenticated and sealed message
    process.stdout.write(headers); // includes terminating line break

    After modifications

    If you want to modify the message before sealing then you have to authenticate the message first and then use authentication results as input for the sealing step.

    const { authenticate, sealMessage } = require('@postalsys/mailauth');
    // 1. authenticate the message
    const { arc, headers } = await authenticate(
        message, // either a String, a Buffer or a Readable Stream
            ip: '', // SMTP client IP
            helo: '', // EHLO/HELO hostname
            mta: '', // server processing this message, defaults to os.hostname()
            sender: '' // MAIL FROM address
    // 2. perform some modifications with the message ...
    // 3. seal the modified message using the initial authentication results
    const sealHeaders = await sealMessage(message, {
        signingDomain: '',
        selector: 'test.rsa',
        privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'),
        // values from the authentication step
        authResults: arc.authResults,
        cv: arc.status.result
    // output authenticated message
    process.stdout.write(sealHeaders); // ARC set
    process.stdout.write(headers); // authentication results


    Brand Indicators for Message Identification (BIMI) support is based on draft-blank-ietf-bimi-01.

    BIMI information is resolved in the authentication step and the results can be found from the bimi property. Message must pass DMARC validation in order to be processed for BIMI. DMARC policy can not be "none" for BIMI to pass.

    const { bimi } = await authenticate(
        message, // either a String, a Buffer or a Readable Stream
            ip: '', // SMTP client IP
            helo: '', // EHLO/HELO hostname
            mta: '', // server processing this message, defaults to os.hostname()
            sender: '' // MAIL FROM address
    if (bimi?.location) {
        console.log(`BIMI location: ${bimi.location}`);

    BIMI-Location header is ignored by mailauth, it is not checked for and it is not modified in any way if it is present. BIMI-Selector is used for selector selection (if available).

    Verified Mark Certificate

    Authority Evidence Document location is available from the bimi.authority property (if set).

    VMC (Verified Mark Certificates) for Authority Evidence Documents is a X509 certificate with an id-pe-logotype extension (oid= that includes a compressed SVG formatted logo file (read more here).

    Some example authority evidence documents:

    You can parse logos from these certificate files by using the parseLogoFromX509 function

    const { parseLogoFromX509 } = require('mailauth/lib/tools');
    let { altnNames, svg } = await parseLogoFromX509(fs.readFileSync('vmc.pem'));

    NB! parseLogoFromX509 does not verify the validity of the VMC certificate. It could be self signed or expired and still be processed.


    mailauth allows you to fetch MTA-STS information for a domain name.

    const { getPolicy, validateMx } = require('mailauth/lib/mta-sts');
    let knownPolicy = getCachedPolicy(''); // optional
    let mx = '';
    const { policy, status } = await getPolicy('', knownPolicy);
    const policyMatch = validateMx(mx, policy);
    if ( !== knownPolicy?.id) {
        // policy has been updated, update cache
    if (policy.mode === 'enforce') {
        // must use TLS
    if ( && !policyMatch) {
        // can't connect, unlisted MX

    Resolve policy

    Resolve MTA-STS policy for a domain

    async getPolicy(domain [,knownPolicy]) -> {policy, status}


    • domain is the domain to check for (eg. "")
    • knownPolicy (optional) is the policy object from last check for this domain. This is used to check if the policy is still valid or it was updated.

    Function returns an object with the following properties:

    • policy (object)
      • id (string or false) ID of the policy
      • mode (string) one of "none", "testing" or "enforce"
      • mx (array, if available) an Array of whitelisted MX hostnames
      • expires (string, if available) ISO date string for cacheing
    • status (string) one of the following values:
      • "not_found" no policy was found for this domain. You can decide yourself how long you want to cache this response
      • "cached" no changes detected, current policy is still valid and can be used
      • "found" new or updated policy was found. Cache this in your system until policy.expires
      • "renew" existing policy is still valid, renew cached version until policy.expires
      • "errored" policy discovery failed for some temporary error (eg. failing DNS queries). See policy.error for details

    Validate MX hostname

    Check if a resolved MX hostname is valid by MTA-STS policy or not

    validateMx(mx, policy) -> Boolean


    • mx is the resolved MX hostname (eg. "")
    • policy is the policy object returned by getPolicy()

    Function returns a boolean. If it is true then MX hostname is allowed to use.


    mailauth uses the following test suites:

    SPF test suite

    OpenSPF test suite ( mirror) with the following differences:

    • No PTR support in mailauth, all PTR related tests are ignored
    • Less strict whitespace checks (mailauth accepts multiple spaces between tags etc)
    • Some macro tests are skipped (macro expansion is supported in most parts)
    • Some tests where invalid component is listed after a matching part (mailauth processes from left to right and returns on first match found)
    • Other than that all tests pass

    ARC test suite from ValiMail

    ValiMail arc_test_suite

    • mailauth is less strict on header tags and casing, for example uppercase S= for a selector passes in mailauth but fails in ValiMail.
    • Signing test suite is used for input only. All listed messages are signed using provided keys but signatures are not matched against reference. Instead mailauth validates the signatures itself and looks for the same cv= output that the ARC-Seal header in the test suite has
    • Other than that all tests pass


    First install the module from npm:

    $ npm install mailauth

    next import any method you want to use from mailauth package into your script:

    const { authenticate } = require('mailauth');


    © 2020 Andris Reinman

    Licensed under MIT license


    npm i mailauth

    DownloadsWeekly Downloads






    Unpacked Size

    221 kB

    Total Files


    Last publish


    • avatar