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.
- README-JA(日本語)
- What is Sisimai
- Setting Up Sisimai
- Usage
- Differences between Go vs. Others
- Contributing
- Other Information
- Author
- Copyright
- License
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.
- 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
- Essential information:
- Output formats
- struct (sisimai/sis.Fact)
- JSON (by using
encoding/json
)
- Sisimai provides detailed insights into bounce emails by extracting 26 key data points.1
- Easy to Install, Use.
$ go get -u libsisimai.org/sisimai@latest
import "libsisimai.org/sisimai"
- High Precision of Analysis
- Support 55 MTAs/MDAs/ESPs
- Support Feedback Loop Message(ARF)
- Can detect 36 bounce reasons
More details about system requirements are available at Sisimai | Getting Started page.
$ 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
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"
}
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) }
}
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) }
}
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.
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)
}
}
}
}
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.
[
{
"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"
}
]
The following table show the differences between the Go version of Sisimai and the other language versions: p5-sisimai and rb-sisimai.
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 |
Please use the issue tracker to report any bugs.
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.
- @libsisimai | Sisimai on Twitter (@libsisimai)
- LIBSISIMAI.ORG | SISIMAI | MAIL ANALYZING INTERFACE | DECODING BOUNCES, BETTER AND FASTER.
- Sisimai Blog | blog.libsisimai.org
- Facebook Page | facebook.com/libsisimai
- GitHub | github.com/sisimai/go-sisimai
- Perl verison | Perl version of Sisimai
- Ruby version | Ruby version of Sisimai
- Fixtures | set-of-emails - Sample emails for "make test"
- README-JA.md - README.md in Japanese(日本語)
- RFC3463 - Enhanced Mail System Status Codes
- RFC3464 - An Extensible Message Format for Delivery Status Notifications
- RFC3834 - Recommendations for Automatic Responses to Electronic Mail
- RFC5321 - Simple Mail Transfer Protocol
- RFC5322 - Internet Message Format
@azumakuniyuki and sisimai development team
Copyright (C) 2014-2025 azumakuniyuki and sisimai development team, All Rights Reserved.
This software is distributed under The BSD 2-Clause License.