Go library for sending mail with the Mailgun API.

Related tags

Email go maigun
Overview

Mailgun with Go

GoDoc Build Status

Go library for interacting with the Mailgun API.

Usage

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/mailgun/mailgun-go/v4"
)

// Your available domain names can be found here:
// (https://app.mailgun.com/app/domains)
var yourDomain string = "your-domain-name" // e.g. mg.yourcompany.com

// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
var privateAPIKey string = "your-private-key"


func main() {
    // Create an instance of the Mailgun Client
    mg := mailgun.NewMailgun(yourDomain, privateAPIKey)

    sender := "[email protected]"
    subject := "Fancy subject!"
    body := "Hello from Mailgun Go!"
    recipient := "[email protected]"

    // The message object allows you to add attachments and Bcc recipients
    message := mg.NewMessage(sender, subject, body, recipient)

    ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    defer cancel()

    // Send the message with a 10 second timeout
    resp, id, err := mg.Send(ctx, message)

    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("ID: %s Resp: %s\n", id, resp)
}

Get Events

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/mailgun/mailgun-go/v4"
    "github.com/mailgun/mailgun-go/v4/events"
)

func main() {
    // You can find the Private API Key in your Account Menu, under "Settings":
    // (https://app.mailgun.com/app/account/security)
    mg := mailgun.NewMailgun("your-domain.com", "your-private-key")

    it := mg.ListEvents(&mailgun.ListEventOptions{Limit: 100})

    var page []mailgun.Event

    // The entire operation should not take longer than 30 seconds
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
    defer cancel()

    // For each page of 100 events
    for it.Next(ctx, &page) {
        for _, e := range page {
            // You can access some fields via the interface
            fmt.Printf("Event: '%s' TimeStamp: '%s'\n", e.GetName(), e.GetTimestamp())

            // and you can act upon each event by type
            switch event := e.(type) {
            case *events.Accepted:
                fmt.Printf("Accepted: auth: %t\n", event.Flags.IsAuthenticated)
            case *events.Delivered:
                fmt.Printf("Delivered transport: %s\n", event.Envelope.Transport)
            case *events.Failed:
                fmt.Printf("Failed reason: %s\n", event.Reason)
            case *events.Clicked:
                fmt.Printf("Clicked GeoLocation: %s\n", event.GeoLocation.Country)
            case *events.Opened:
                fmt.Printf("Opened GeoLocation: %s\n", event.GeoLocation.Country)
            case *events.Rejected:
                fmt.Printf("Rejected reason: %s\n", event.Reject.Reason)
            case *events.Stored:
                fmt.Printf("Stored URL: %s\n", event.Storage.URL)
            case *events.Unsubscribed:
                fmt.Printf("Unsubscribed client OS: %s\n", event.ClientInfo.ClientOS)
            }
        }
    }
}

Event Polling

The mailgun library has built-in support for polling the events api

package main

import (
    "context"
    "time"

    "github.com/mailgun/mailgun-go/v4"
)

func main() {
    // You can find the Private API Key in your Account Menu, under "Settings":
    // (https://app.mailgun.com/app/account/security)
    mg := mailgun.NewMailgun("your-domain.com", "your-private-key")

    begin := time.Now().Add(time.Second * -3)

    // Very short poll interval
    it := mg.PollEvents(&mailgun.ListEventOptions{
        // Only events with a timestamp after this date/time will be returned
        Begin: &begin,
        // How often we poll the api for new events
        PollInterval: time.Second * 30,
    })

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // Poll until our email event arrives
    var page []mailgun.Event
    for it.Poll(ctx, &page) {
        for _, e := range page {
            // Do something with event
        }
    }
}

Email Validations

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/mailgun/mailgun-go/v4"
)

// If your plan does not include email validations but you have an account,
// use your Public Validation api key. If your plan does include email validations,
// use your Private API key. You can find both the Private and
// Public Validation API Keys in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
var apiKey string = "your-api-key"

func main() {
    // To use the /v4 version of validations define MG_URL in the environment
    // as `https://api.mailgun.net/v4` or set `v.SetAPIBase("https://api.mailgun.net/v4")`

    // Create an instance of the Validator
    v := mailgun.NewEmailValidator(apiKey)

    ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    defer cancel()

    email, err := v.ValidateEmail(ctx, "[email protected]", false)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Valid: %t\n", email.IsValid)
}

Webhook Handling

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "time"

    "github.com/mailgun/mailgun-go/v4"
    "github.com/mailgun/mailgun-go/v4/events"
)

func main() {

    // You can find the Private API Key in your Account Menu, under "Settings":
    // (https://app.mailgun.com/app/account/security)
    mg := mailgun.NewMailgun("your-domain.com", "private-api-key")

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

        var payload mailgun.WebhookPayload
        if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
            fmt.Printf("decode JSON error: %s", err)
            w.WriteHeader(http.StatusNotAcceptable)
            return
        }

        verified, err := mg.VerifyWebhookSignature(payload.Signature)
        if err != nil {
            fmt.Printf("verify error: %s\n", err)
            w.WriteHeader(http.StatusNotAcceptable)
            return
        }

        if !verified {
            w.WriteHeader(http.StatusNotAcceptable)
            fmt.Printf("failed verification %+v\n", payload.Signature)
            return
        }

        fmt.Printf("Verified Signature\n")

        // Parse the event provided by the webhook payload
        e, err := mailgun.ParseEvent(payload.EventData)
        if err != nil {
            fmt.Printf("parse event error: %s\n", err)
            return
        }

        switch event := e.(type) {
        case *events.Accepted:
            fmt.Printf("Accepted: auth: %t\n", event.Flags.IsAuthenticated)
        case *events.Delivered:
            fmt.Printf("Delivered transport: %s\n", event.Envelope.Transport)
        }
    })

    fmt.Println("Serve on :9090...")
    if err := http.ListenAndServe(":9090", nil); err != nil {
        fmt.Printf("serve error: %s\n", err)
        os.Exit(1)
    }
}

Using Templates

Templates enable you to create message templates on your Mailgun account and then populate the data variables at send-time. This allows you to have your layout and design managed on the server and handle the data on the client. The template variables are added as a JSON stringified X-Mailgun-Variables header. For example, if you have a template to send a password reset link, you could do the following:

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/mailgun/mailgun-go/v4"
)

// Your available domain names can be found here:
// (https://app.mailgun.com/app/domains)
var yourDomain string = "your-domain-name" // e.g. mg.yourcompany.com

// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
var privateAPIKey string = "your-private-key"


func main() {
    // Create an instance of the Mailgun Client
    mg := mailgun.NewMailgun(yourDomain, privateAPIKey)

    sender := "[email protected]"
    subject := "Fancy subject!"
    body := ""
    recipient := "[email protected]"

    // The message object allows you to add attachments and Bcc recipients
        message := mg.NewMessage(sender, subject, body, recipient)
        message.SetTemplate("passwordReset")
        message.AddTemplateVariable("passwordResetLink", "some link to your site unique to your user")

    ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    defer cancel()

    // Send the message with a 10 second timeout
    resp, id, err := mg.Send(ctx, message)

    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("ID: %s Resp: %s\n", id, resp)
}

The official mailgun documentation includes examples using this library. Go here and click on the "Go" button at the top of the page.

EU Region

European customers will need to change the default API Base to access your domains

mg := mailgun.NewMailgun("your-domain.com", "private-api-key")
mg.SetAPIBase(mailgun.APIBaseEU)

Installation

If you are using golang modules make sure you include the /v4 at the end of your import paths

$ go get github.com/mailgun/mailgun-go/v4

If you are not using golang modules, you can drop the /v4 at the end of the import path. As long as you are using the latest 1.10 or 1.11 golang release, import paths that end in /v4 in your code should work fine even if you do not have golang modules enabled for your project.

$ go get github.com/mailgun/mailgun-go

NOTE for go dep users

Using version 3 of the mailgun-go library with go dep currently results in the following error

"github.com/mailgun/mailgun-go/v4/events", which contains malformed code: no package exists at ...

This is a known bug in go dep. You can follow the PR to fix this bug here Until this bug is fixed, the best way to use version 3 of the mailgun-go library is to use the golang community supported golang modules.

Testing

WARNING - running the tests will cost you money!

To run the tests various environment variables must be set. These are:

  • MG_DOMAIN is the domain name - this is a value registered in the Mailgun admin interface.
  • MG_PUBLIC_API_KEY is the Public Validation API key - you can get this value from the Mailgun security page
  • MG_API_KEY is the Private API key - you can get this value from the Mailgun security page
  • MG_EMAIL_TO is the email address used in various sending tests.

and finally

  • MG_SPEND_MONEY if this value is set the part of the test that use the API to actually send email will be run - be aware this will count on your quota and this will cost you money.

The code is released under a 3-clause BSD license. See the LICENSE file for more information.

Comments
  • go get package fails

    go get package fails

    go get github.com/mailgun/mailgun-go/v3/

    package github.com/mailgun/mailgun-go/v3: cannot find package "github.com/mailgun/mailgun-go/v3" in any of: /usr/lib/go/src/github.com/mailgun/mailgun-go/v3 (from $GOROOT) /home/shaumux/belong/go/mercury/src/github.com/mailgun/mailgun-go/v3 (from $GOPATH)

    go env

    GOARCH="amd64" GOBIN="" GOCACHE="/home/shaumux/.cache/go-build" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOOS="linux" GOPATH="/home/shaumux/belong/go/mercury" GOPROXY="" GORACE="" GOROOT="/usr/lib/go" GOTMPDIR="" GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64" GCCGO="gccgo" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build459313934=/tmp/go-build -gno-record-gcc-switches"

    This causes the whole thing to fail with dep as it searches for even sub package inside v3/ subpackage

    opened by shaumux 14
  • Clarify (and rename?) API keys required

    Clarify (and rename?) API keys required

    The current mailgun-go requires two keys to be specified:

    • API Key
    • Public API Key

    Mailgun has recently (?) changed the names of these keys, so the "Public API Key" is now called "Email Validation Key". Also, they call the "API Key" "Active API Key".

    Maybe the docs should be updated to reflect this?

    And it would be good if it was enough to specify only the "API Key" if validation is not used in the mailgun-go API.

    opened by boivie 13
  • Bounce Code Type Issue

    Bounce Code Type Issue

    In the Bounce struct of bounces.go, the Mailgun API returns a Code of type Int or String.

    To duplicate, issue gun.GetBounces(-1, -1). If any bounces in the first page of 100 results contain a string and int in the Bounce, you'll get a type error when the json library unmarshalls the Code result to the Bounce struct.

    "Error: json: cannot unmarshal string into Go value of type int"

    opened by travelton 11
  • API base URL cannot be changed

    API base URL cannot be changed

    The base API URL seems to be pointed to the US API currently, with no indication that you need to change it to point to EU if your domain is set there. Instead, you're met with a 404 error telling you your domain is not found.

    When attempting to call SetAPIBase() to set the URL to the EU API, you are unable to include the https:// because you will get this error: invalid character 'M' looking for beginning of value. If you leave out the scheme thinking it will be set by default, you get a POST failure due to the missing scheme: unsupported protocol scheme.

    Edit: Turns out the trailing /v3 is essential for this to work. This is very confusing, and there doesn't seem to be any direct documentation telling you to switch region, or how to properly. Perhaps the mailgun.NewMailgun() should take a third, regional type to specify which API endpoint to be hitting?

    V4 
    opened by henhouse 10
  • GetStoredMessage fails with

    GetStoredMessage fails with "Please use URLs as they are given in events/webhooks"

    When passing in the storage.key of an event to the GetStoredMessage function, I get something like:

    UnexpectedResponseError
    URL=https://api.mailgun.net/v3/domains/example.com/messages/STORAGEKEY
    ExpectedOneOf=[]int{200, 202, 204} Got=400
    Error: {"message":"Please use URLs as they are given in events/webhooks"}
    

    The URL provided by the event's storage.url uses a slightly different domain:

    https://so.api.mailgun.net/v3/domains/example.com/messages/STORAGEKEY
    

    Which, if followed (with proper credentials) gives the proper response.

    The only difference from the URL generated by GetStoredMessage is the "so." sub-sub-domain, for what that is worth.

    bug help wanted 
    opened by daBrado 9
  • App Engine support

    App Engine support

    mailgun-go depends on simplehttp, whose App Engine support is unfortunately broken: https://github.com/mbanzon/simplehttp/issues/7

    So mailgun-go isn't currently suitable for use on App Engine.

    bug enhancement 
    opened by ohler 9
  • Handling rate limits

    Handling rate limits

    Hi there, thanks for the work on this library.

    I support an upstream library (https://github.com/phillbaker/terraform-provider-mailgunv3/) and there has been a question about handling rate-limits (http 429s) (https://github.com/phillbaker/terraform-provider-mailgunv3/issues/15).

    Exponential backoff seems to be the path forward - but is that something that makes sense to handle here in the client or in downstream libraries?

    Thanks!

    V4 
    opened by phillbaker 7
  • VerifyWebhookRequest always return false

    VerifyWebhookRequest always return false

    I used the method in this package and it always return false, including from the mailgun webhook tests.

    I believe the calls to req.FormValue(xxx) are the problem, it returns empty for all values. https://github.com/mailgun/mailgun-go/blob/4071f66f00fb74b08ae720f28ee9fcd98ebfd69e/webhooks.go#L82 https://github.com/mailgun/mailgun-go/blob/4071f66f00fb74b08ae720f28ee9fcd98ebfd69e/webhooks.go#L83 https://github.com/mailgun/mailgun-go/blob/4071f66f00fb74b08ae720f28ee9fcd98ebfd69e/webhooks.go#L86

    opened by vincent-hatakeyama 7
  • Allow only CC or BCC recipients

    Allow only CC or BCC recipients

    Currently the validation in messages.go requires at least one recipient in the to field. However, I should be permitted to send an email as long as there is at least one recipient in the to, cc, OR bcc field.

    opened by inoda 7
  • EmailValidation Update

    EmailValidation Update

    New features have been added, and now the api is paid for: http://blog.mailgun.com/mailgun-rolls-out-changes-to-email-validation-api-including-new-pricing-model-and-features/

    New return:

    {
        "address": "[email protected]",
        "did_you_mean": null,
        "is_disposable_address": false,
        "is_role_address": true,
        "is_valid": true,
        "parts": {
            "display_name": null,
            "domain": "mailgun.net",
            "local_part": "foo"
        }
    }
    

    http://mailgun-documentation.readthedocs.io/en/latest/api-email-validation.html

    enhancement 
    opened by ciceroverneck 7
  • Add travis or any other CI for tests

    Add travis or any other CI for tests

    Hi,

    do you plan to implement a CI test environment like travis, wercker, ...? It's free and it's easier to check the contributed PRs. You could, for example, check for test coverage, formatting and many more.

    If you want, I will contribute travis or wecker integration.

    Thanks and

    best regards

    question help wanted 
    opened by sbani 7
  • Allow to use : in domain for testing

    Allow to use : in domain for testing

    Hey. I'm trying to write a test using a mailgun mock server:

    mailgunServer := mailgun.NewMockServer()
    mailgunClient := mailgun.NewMailgun(mailgunServer.URL(), "")
    

    However, I'm getting an error from this place:

    https://github.com/mailgun/mailgun-go/blob/master/messages.go#L557:

    invalidChars := ":&'@(),!?#;%+=<>"
    if i := strings.ContainsAny(mg.domain, invalidChars); i {
        err = fmt.Errorf("you called Send() with a domain that contains invalid characters")
        return
    }
    

    Can we allow : in the domain for testing purposes? I can create a PR if needed

    opened by ivaaaan 0
  • Add support for batch endpoints for adding bounces and complaints

    Add support for batch endpoints for adding bounces and complaints

    Adds AddComplaints and AddBounces methods to support the batch mode for the associated endpoints

    https://documentation.mailgun.com/en/latest/api-suppressions.html#add-multiple-complaints https://documentation.mailgun.com/en/latest/api-suppressions.html#add-multiple-bounces

    opened by hownowstephen 0
Releases(v4.8.1)
  • v4.8.1(May 27, 2022)

    What's Changed

    • Added special case for Host headers by @thrawn01 in https://github.com/mailgun/mailgun-go/pull/289

    Full Changelog: https://github.com/mailgun/mailgun-go/compare/v4.8.0...v4.8.01

    Source code(tar.gz)
    Source code(zip)
  • v4.8.0(May 27, 2022)

    What's Changed

    • Added CaptureCurlOutput by @thrawn01 in https://github.com/mailgun/mailgun-go/pull/288

    Full Changelog: https://github.com/mailgun/mailgun-go/compare/v4.7.0...v4.8.0

    Source code(tar.gz)
    Source code(zip)
  • v4.7.0(May 27, 2022)

    What's Changed

    • Added AddOverrideHeader() by @thrawn01 in https://github.com/mailgun/mailgun-go/pull/287

    Full Changelog: https://github.com/mailgun/mailgun-go/compare/v4.6.2...v4.7.0

    Source code(tar.gz)
    Source code(zip)
  • v4.6.2(May 23, 2022)

    What's Changed

    • README improvement for eu-domains by @DionTech in https://github.com/mailgun/mailgun-go/pull/285
    • Changed url validation by @tobias-kuendig in https://github.com/mailgun/mailgun-go/pull/286

    New Contributors

    • @DionTech made their first contribution in https://github.com/mailgun/mailgun-go/pull/285
    • @tobias-kuendig made their first contribution in https://github.com/mailgun/mailgun-go/pull/286

    Full Changelog: https://github.com/mailgun/mailgun-go/compare/v4.6.1...v4.6.2

    Source code(tar.gz)
    Source code(zip)
  • v4.6.1(Mar 29, 2022)

  • v4.6.0(Nov 17, 2021)

    • https://github.com/mailgun/mailgun-go/pull/269 Add a struct and a function to parse route forward hook payloads.
    • https://github.com/mailgun/mailgun-go/pull/271 Add missing VerifyAndReturnDomain method to the interface.
    • improve docs
    Source code(tar.gz)
    Source code(zip)
  • v4.5.3(Aug 30, 2021)

  • v4.5.2(Jun 28, 2021)

  • v4.5.1(Apr 12, 2021)

  • v4.5.0(Apr 9, 2021)

  • v4.4.1(Feb 23, 2021)

  • v4.4.0(Feb 23, 2021)

  • v4.3.4(Feb 22, 2021)

  • v4.3.3(Jan 29, 2021)

  • v4.3.2(Jan 19, 2021)

  • v4.3.1(Jan 9, 2021)

  • v4.3.0(Oct 14, 2020)

    [4.3.0] - 2020-10-14

    Changed

    • Replaced easyjson with json-iterator when marshaling events
    • Modified the mailgun.Event interface by removing the Marshaller interface from easyjson.
    • Fixed failure while testing webhook via mailgun web console
    Source code(tar.gz)
    Source code(zip)
  • v4.2.0(Sep 17, 2020)

Owner
Mailgun Team
Mailgun Team
Mail-alias-resolve-ldap - Resolve mail alias addresses in LDAP

alias-resolve-ldap alias-resolve-ldap was written to be used as a hook for the c

Björn Busse 1 Jan 30, 2022
Mail_sender - This library is for sending emails from your mail

Mail Sender This library is for sending emails from your mail Installation mail_

null 1 Dec 30, 2021
Golang package that generates clean, responsive HTML e-mails for sending transactional mail

Hermes Hermes is the Go port of the great mailgen engine for Node.js. Check their work, it's awesome! It's a package that generates clean, responsive

Mathieu Cornic 2.6k Dec 28, 2022
📧 Example of sending mail via SendGrid in Golang.

?? go-sendgrid-example Example of sending mail via SendGrid in Golang. Get it started $ make setup # Edit environment variables $ vim ./env/local.env

ducci 1 Jan 11, 2022
Sending emails using email server talking to RabbitMQ and send grid server sending emails to email ids consumed from RabbitMQ

Sending emails using email server talking to RabbitMQ and send grid server sending emails to email ids consumed from RabbitMQ

Shivanshu Raj Shrivastava 2 Oct 27, 2022
:envelope: A streaming Go library for the Internet Message Format and mail messages

go-message A Go library for the Internet Message Format. It implements: RFC 5322: Internet Message Format RFC 2045, RFC 2046 and RFC 2047: Multipurpos

Simon Ser 288 Dec 26, 2022
Mcopa - A library allows for parsing an email message into a more convenient form than the net/mail provides

Mail content parsing This library allows for parsing an email message into a mor

Mr Chen 0 Jan 1, 2022
Inline styling for html mail in golang

go-premailer Inline styling for HTML mail in golang Document install go get github.com/vanng822/go-premailer/premailer Example import ( "fmt" "gith

Nguyen Van Nhu 96 Nov 30, 2022
✉️ Composable all-in-one mail server.

Maddy Mail Server Composable all-in-one mail server. Maddy Mail Server implements all functionality required to run a e-mail server. It can send messa

Max Mazurov 3.7k Dec 27, 2022
MIME mail encoding and decoding package for Go

enmime enmime is a MIME encoding and decoding library for Go, focused on generating and parsing MIME encoded emails. It is being developed in tandem w

James Hillyerd 338 Nov 30, 2022
an MDA that sends a webhook on recieval of mail

an MDA that sends a webhook on recieval of mail

forlater 10 Aug 13, 2022
Filtering spam in mail server, protecting both client privacy and server algorithm

HE Spamfilter SNUCSE 2021 "Intelligent Computing System Design Project" Hyesun Kwak Myeonghwan Ahn Dongwon Lee abstract Naïve Bayesian spam filtering

Myeonghwan Ahn 1 Mar 23, 2022
Simple tool to test SMTP mail send with various settings including TLS1.1 downgrade

smtptest Simple tool to test SMTP mail send with various settings including TLS1.1 downgrade All settings are configurable in the config.yaml file ser

Mario 1 Sep 19, 2022
Go-mail - Email service using Kafka consumer

?? The Project This project consists in a Kafka consumer that reads messages of

Gustavo Belmonte Cioccari 2 Feb 5, 2022
Send markdown files as MIME-encoded electronic mail.

Send markdown files as MIME-encoded electronic mail.

Dmitry Kotik 5 Aug 9, 2022
:white_check_mark: A Go library for email verification without sending any emails.

email-verifier ✉️ A Go library for email verification without sending any emails. Features Email Address Validation: validates if a string contains a

AfterShip Ltd. 601 Dec 30, 2022
✉️ A Go library for email verification without sending any emails.

email-verifier ✉️ A Go library for email verification without sending any emails. Features Email Address Validation: validates if a string contains a

AfterShip Ltd. 248 Jun 24, 2021
Golang library for sending email using gmail credentials

library for sending email using gmail credentials

@Yassine Messaoudi 0 Jan 22, 2022
A simple microservice designed in Go using Echo Microframework for sending emails and/or calendar invitations to users.

Calenvite A simple microservice designed in GO using Echo Microframework for sending emails and/or calendar invitations to users. Features Send emails

Adriano Galello 13 Oct 29, 2022