Skip to content

Go language version of Sisimai: Under the development and may be released this summer

License

Notifications You must be signed in to change notification settings

sisimai/go-sisimai

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

License

Caution

The Go language version of sisimai is currently in Public Beta. The API of libsisimai.org/sisimai and internal specifications are subject to significant changes until the official v5.2.0 release.

Note

Sisimai is a Go package but it can be used in any environment that JSON can be read, such as PHP, Java, Python, and Rust. By obtaining the analysis results, it is very useful for understanding the bounce occurrence status.

What is Sisimai

Sisimai is a Go package, is a library that decodes complex and diverse bounce emails and outputs the results of the delivery failure, such as the reason for the bounce and the recipient email address, in structured data. It is also possible to output in JSON format.

The key features of Sisimai

  • Decode email bounces to structured data
    • Sisimai provides detailed insights into bounce emails by extracting 26 key data points.1
      • Essential information: Timestamp, Origin
      • Sender information: Addresser, SenderDomain,
      • Recipient information: Recipient, Destination, Alias
      • Delivery information: Action, ReplyCode, DeliveryStatus, Command
      • Bounce details: Reason, DiagnosticCode, DiagnosticType, FeedbackType, FeedbackID, HardBounce
      • Message details: Subject, MessageID, ListID,
      • Additional information: DecodedBy, TimezoneOffset, Lhost, Rhost, Token, Catch
    • Output formats
  • Easy to Install, Use.
    • $ go get -u libsisimai.org/sisimai@latest
    • import "libsisimai.org/sisimai"
  • High Precision of Analysis

Setting Up Sisimai

System requirements

More details about system requirements are available at Sisimai | Getting Started page.

Install and Build

Install

$ mkdir ./sisimai
$ cd ./sisimai
$ go mod init example.com/sisimaicli
go: creating new go.mod: module example.com/sisimaicli

$ go get -u libsisimai.org/sisimai@latest
go: added golang.org/x/net v0.35.0
go: added golang.org/x/text v0.22.0
go: added libsisimai.org/sisimai v0.0.1

Build

For example, the following sisid.go: sisimai decoder is a minimal program that decodes bounce emails and outputs them in JSON format.

$ vi ./sisid.go
// sisimai decoder program
package main
import "os"
import "fmt"
import "libsisimai.org/sisimai"

func main() {
    path := os.Args[1]
    args := sisimai.Args()

    sisi, nyaan := sisimai.Rise(path, args)
    for _, e := range *sisi {
        cv, _ := e.Dump()
        fmt.Printf("%s\n",cv)
    }
    if len(*nyaan) > 0 { fmt.Frpintf(os.Stderr, "%v\n", *nyaan) }
}

Once you have written sisid.go, build an executable binary with go build command.

$ CGO_ENABLED=0 go build -o ./sisid ./sisid.go

Specifying the path to a bounce email (or Maildir/) as the first argument will output the decoded results as a JSON string.

$ ./sisid ./path/to/bounce-mail.eml | jq
{
  "addresser": "michistuna@example.org",
  "recipient": "kijitora@google.example.com",
  "timestamp": 1650119685,
  "action": "failed",
  "alias": "contact@example.co.jp",
  "catch": null,
  "decodedby": "Postfix",
  "deliverystatus": "5.7.26",
  "destination": "google.example.com",
  "diagnosticcode": "host gmail-smtp-in.l.google.com[64.233.187.27] said: This mail has been blocked because the sender is unauthenticated. Gmail requires all senders to authenticate with either SPF or DKIM. Authentication results: DKIM = did not pass SPF [relay3.example.com] with ip: [192.0.2.22] = did not pass For instructions on setting up authentication, go to https://support.google.com/mail/answer/81126#authentication c2-202200202020202020222222cat.127 - gsmtp (in reply to end of DATA command)",
  "diagnostictype": "SMTP",
  "feedbackid": "",
  "feedbacktype": "",
  "hardbounce": false,
  "lhost": "relay3.example.com",
  "listid": "",
  "messageid": "hwK7pzjzJtz0RF9Y@relay3.example.com",
  "origin": "./path/to/bounce-mail.eml",
  "reason": "authfailure",
  "rhost": "gmail-smtp-in.l.google.com",
  "replycode": "550",
  "command": "DATA",
  "senderdomain": "google.example.com",
  "subject": "Nyaan",
  "timezoneoffset": "+0900",
  "token": "5253e9da9dd67573851b057a89cbcf41293e99bf"
}

Usage

Basic usage

libsisimai.org/sisimai.Rise() function provides the feature for getting decoded data as *[]sis.Fact struct, occurred errors as *[]sis.NotDecoded from bounced email messages as the following.

package main
import "os"
import "fmt"
import "libsisimai.org/sisimai"

func main() {
    path := os.Args[1]     // go run ./sisid /path/to/mailbox or maildir/
    args := sisimai.Args() // sis.DecodingArgs{}

    // If you also need analysis results that are "delivered" (successfully delivered),
    // set `true` into the "Delivered" option for the Rise() function as shown below.
    args.Delivered = true

    // If you also need analysis results that show a "vacation" reason, set `true` into
    // the "Vacation" option for the Rise() function as shown in the following code.
    args.Vacation  = true

    // sisi is a pointer to []sis.Fact
    sisi, nyaan := sisimai.Rise(path, args)
    if len(*sisi) > 0 {
        for _, e := range *sisi {
            // e is a sis.Fact struct
            fmt.Printf("- Sender is %s\n", e.Addresser.Address)
            fmt.Printf("- Recipient is %s\n", e.Recipient.Address)
            fmt.Printf("- Bounced due to %s\n", e.Reason)
            fmt.Printf("- With the error message: %s\n", e.DiagnosticCode)

            cv, _ := e.Dump()     // Convert the decoded data to JSON string
            fmt.Printf("%s\n",cv) // JSON formatted string the jq command can read
        }
    }
    // nyaan is a pointer to []sis.NotDecoded
    if len(*nyaan) > 0 { fmt.Fprintf(os.Stderr, "%v\n", *nyaan) }
}

Convert to JSON

The following code snippet illustrates the use of the libsisimai.org/sisimai.Dump() function to obtain decoded bounce email data in JSON array format.

package main
import "os"
import "fmt"
import "libsisimai.org/sisimai"

func main() {
    path := os.Args[1]
    args := sisimai.Args()

    json, nyaan := sisimai.Dump(path, args)
    if json != nil && *json != "" { fmt.Printf("%s\n", *json)   }
    if len(*nyaan) > 0 { fmt.Fprintf(os.Stderr, "%v\n", *nyaan) }
}

Callback feature

sis.DecodingArgs have the Callback0 and Callback1 fields for keeping callback functions. The former is called at message.sift() for dealitng email headers and entire message body. The latter is called at the end of each email file processing inside of sisimai.Rise().

The results generated by the callback functions are accessible via Catch field defined in sis.Fact.

Callback0: For email headers and the body

The function set in args.Callback0 is called at message.sift().

package main
import "os"
import "fmt"
import "strings"
import "libsisimai.org/sisimai"

func main() {
    path := os.Args[1]     // go run ./sisid /path/to/mailbox or maildir/
    args := sisimai.Args() // sis.DecodingArgs{}

    args.Callback0 = func(arg *sisimai.CallbackArgs) (map[string]interface{}, error) {
        // - This function allows users to add custom processing to the email before parsing.
        // - For example, you can extract the delivery ID from the "X-Delivery-App-ID:" header
        //   and store it in the data map like this: data["x-delivery-app-id"] = "neko22-2".
        // - The library executes this function and assigns the return value to the Catch field of the Fact struct.
        // - Users can then retrieve and access the data from Catch by type assertion in the caller.
        name := "X-Delivery-App-ID"
        data := make(map[string]interface{})
        data[strings.ToLower(name)] = ""

        if arg.Payload != nil && len(*arg.Payload) > 0 {
            mesg := *arg.Payload
            if p0 := strings.Index(mesg, "\n" + name + ":"); p0 > 0 {
                cw := p0 + len(name) + 2
                p1 := strings.Index(mesg[cw:], "\n")
                if p1 > 0 {
                    data[strings.ToLower(name)] = mesg[cw + 1:cw + p1]
                }
            }
        }
        return data, nil
    }

    sisi, _ := sisimai.Rise(path, args)
    if len(*sisi) > 0 {
        for _, e := range *sisi {
            // e is a sis.Fact struct
            re, as := e.Catch.(map[string]interface{})
            if as == false { continue }
            if ca, ok := re["x-delivery-app-id"].(string); ok {
                fmt.Printf("- Catch[X-Delivery-App-ID] = %s\n", ca)
            }
        }
    }
}

Callback1: For each email file

The function set in args.Callback1 is called at sisimai.Rise() function for dealing each email file after decoding each bounce message.

package main
import "os"
import "io/ioutil"
import "libsisimai.org/sisimai"

func main() {
    path := os.Args[1]     // go run ./sisid /path/to/mailbox or maildir/
    args := sisimai.Args() // sis.DecodingArgs{}

    args.Callback1 = func(arg *sisimai.CallbackArgs) (bool, error) {
        // - This function defines custom operations that the user wants to perform on the parsed email file.
        // - For example, you can write the contents of the parsed bounce email to a file in /tmp/.
        if nyaan := ioutil.WriteFile("/tmp/copy.eml", []byte(*arg.Payload), 0400); nyaan != nil {
            return false, nyaan
        }
        return true, nil
    }

    // sisi is a pointer to []sis.Fact
    sisi, nyaan := sisimai.Rise(path, args)
    if len(*sisi) > 0 {
        for _, e := range *sisi {
            // e is a sis.Fact struct
            ...
        }
    }
}

More information about the callback feature is available at Sisimai | How To Parse - Callback Page.

Output example

[
  {
    "addresser": "michistuna@example.org",
    "recipient": "kijitora@google.example.com",
    "timestamp": 1650119685,
    "action": "failed",
    "alias": "contact@example.co.jp",
    "catch": null,
    "decodedby": "Postfix",
    "deliverystatus": "5.7.26",
    "destination": "google.example.com",
    "diagnosticcode": "host gmail-smtp-in.l.google.com[64.233.187.27] said: This mail has been blocked because the sender is unauthenticated. Gmail requires all senders to authenticate with either SPF or DKIM. Authentication results: DKIM = did not pass SPF [relay3.example.com] with ip: [192.0.2.22] = did not pass For instructions on setting up authentication, go to https://support.google.com/mail/answer/81126#authentication c2-202200202020202020222222cat.127 - gsmtp (in reply to end of DATA command)",
    "diagnostictype": "SMTP",
    "feedbackid": "",
    "feedbacktype": "",
    "hardbounce": false,
    "lhost": "relay3.example.com",
    "listid": "",
    "messageid": "hwK7pzjzJtz0RF9Y@relay3.example.com",
    "origin": "./path/to/bounce-mail.eml",
    "reason": "authfailure",
    "rhost": "gmail-smtp-in.l.google.com",
    "replycode": "550",
    "command": "DATA",
    "senderdomain": "google.example.com",
    "subject": "Nyaan",
    "timezoneoffset": "+0900",
    "token": "5253e9da9dd67573851b057a89cbcf41293e99bf"
  }
]

Differences between Go and Others

The following table show the differences between the Go version of Sisimai and the other language versions: p5-sisimai and rb-sisimai.

Features

Features Go Perl Ruby / JRuby
System requirements 1.17 - 5.26 - 2.4 - / 9.2 -
Dependencies (Except standard libs) 2 packages 2 modules 1 gem
Source lines of code 9,600 lines 9,900 lines 9,800 lines
The number of tests 134,000 tests 319,000 tests 410,000 tests
The number of bounce emails decoded/sec 2 1200 emails 400 emails 340 emails
License 2 Clause BSD 2 Caluse BSD 2 Clause BSD
Commercial support Available Available Available

Contributing

Bug report

Please use the issue tracker to report any bugs.

Emails could not be decoded

Bounce emails that couldn't be decoded by the latest version of sisimai are saved in the repository set-of-emails/to-be-debugged-because/sisimai-cannot-parse-yet. If you have found any bounce email cannot be decoded using sisimai, please add the email into the directory and send Pull-Request to this repository.

Other Information

Related sites

See also

Author

@azumakuniyuki and sisimai development team

Copyright

Copyright (C) 2014-2025 azumakuniyuki and sisimai development team, All Rights Reserved.

License

This software is distributed under The BSD 2-Clause License.

Footnotes

  1. The callback function allows you to add your own data under the Catch field.

  2. macOS Monterey/1.6GHz Dual-Core Intel Core i5/16GB-RAM/Go 1.22/Perl 5.30

About

Go language version of Sisimai: Under the development and may be released this summer

Resources

License

Stars

Watchers

Forks

Packages

No packages published