77 "fmt"
88 "net/mail"
99 "strings"
10+ "time"
1011
1112 jsoniter "github.com/json-iterator/go"
1213 "github.com/mailgun/mailgun-go/v5"
@@ -19,17 +20,17 @@ import (
1920)
2021
2122type Mailgun struct {
22- client * mailgun.Client
23- confer services.Configurer [* mailgun.PlainMessage ]
23+ apiKeys []mmailer.ApiKey
24+ webhookSigningKey string
25+ confer services.Configurer [* mailgun.PlainMessage ]
2426}
2527
26- func New (apiKey string , webhookSigningKey string ) * Mailgun {
28+ func New (apiKeys []mmailer. ApiKey , webhookSigningKey string ) * Mailgun {
2729 mg := & Mailgun {
28- client : mailgun .NewMailgun (apiKey ),
29- confer : configurer {},
30+ apiKeys : apiKeys ,
31+ webhookSigningKey : webhookSigningKey ,
32+ confer : configurer {},
3033 }
31- mg .client .SetWebhookSigningKey (webhookSigningKey )
32- _ = mg .client .SetAPIBase (mailgun .APIBaseEU )
3334 return mg
3435}
3536
@@ -50,10 +51,23 @@ func (m *Mailgun) CanSend(e mmailer.Email) bool {
5051 return false
5152 }
5253 }
53- if ! strings .HasSuffix (e .From .Email , "strictlog.modfin.se" ) {
54- return false
54+ _ , ok := mmailer .KeyByEmailDomain (m .apiKeys , e .From .Email )
55+ return ok
56+ }
57+
58+ func (m * Mailgun ) newClient (addr string ) (* mailgun.Client , error ) {
59+ k , ok := mmailer .KeyByEmailDomain (m .apiKeys , addr )
60+ if ! ok {
61+ return nil , errors .New ("sendgrid: no api key found for " + addr )
62+ }
63+ client := mailgun .NewMailgun (k .Key )
64+ if k .Props != nil && k .Props ["region" ] == "eu" {
65+ err := client .SetAPIBase (mailgun .APIBaseEU )
66+ if err != nil {
67+ return nil , fmt .Errorf ("failed to set EU region" )
68+ }
5569 }
56- return true
70+ return client , nil
5771}
5872
5973func (m * Mailgun ) Send (ctx context.Context , e mmailer.Email ) ([]mmailer.Response , error ) {
@@ -66,7 +80,10 @@ func (m *Mailgun) Send(ctx context.Context, e mmailer.Email) ([]mmailer.Response
6680 if domain == "" {
6781 return nil , fmt .Errorf ("mailgun: failed to get email domain: %v" , from .Address )
6882 }
69-
83+ client , err := m .newClient (domain )
84+ if err != nil {
85+ return nil , fmt .Errorf ("mailgun: failed to create client: %w" , err )
86+ }
7087 to := slicez .Map (e .To , func (a mmailer.Address ) string {
7188 return a .String ()
7289 })
@@ -92,7 +109,7 @@ func (m *Mailgun) Send(ctx context.Context, e mmailer.Email) ([]mmailer.Response
92109 msg .AddBufferAttachment (a .Name , b )
93110 }
94111
95- resp , err := m . client .Send (ctx , msg )
112+ resp , err := client .Send (ctx , msg )
96113 if err != nil {
97114 return nil , fmt .Errorf ("mailgun: failed to send email: %w" , err )
98115 }
@@ -115,7 +132,9 @@ func (m *Mailgun) UnmarshalPosthook(body []byte) ([]mmailer.Posthook, error) {
115132 if err := jsoniter .Unmarshal (body , & webhook ); err != nil {
116133 return nil , err
117134 }
118- verified , err := m .client .VerifyWebhookSignature (webhook .Signature )
135+ client := mailgun .NewMailgun ("" ) // api key is not used for VerifyWebhookSignature
136+ client .SetWebhookSigningKey (m .webhookSigningKey )
137+ verified , err := client .VerifyWebhookSignature (webhook .Signature )
119138 if err != nil {
120139 return nil , fmt .Errorf ("mailgun: failed to verify signature: %w" , err )
121140 }
@@ -146,6 +165,7 @@ func (m *Mailgun) UnmarshalPosthook(body []byte) ([]mmailer.Posthook, error) {
146165 h .Event = mmailer .EventDelivered
147166 h .MessageId = e .Message .Headers .MessageID
148167 h .Email = e .Recipient
168+ h .Info = infoString (false , "" , "" , e .DeliveryStatus )
149169
150170 case * events.Opened :
151171 h .Event = mmailer .EventOpen
@@ -156,17 +176,16 @@ func (m *Mailgun) UnmarshalPosthook(body []byte) ([]mmailer.Posthook, error) {
156176 switch e .Severity {
157177 case "permanent" :
158178 h .Event = mmailer .EventBounce
159-
160- case "temporary" :
161- h .Event = mmailer .EventDeferred
162179 if e .Reason == "suppress-bounce" {
163180 h .Event = mmailer .EventDropped
164181 }
182+ case "temporary" :
183+ h .Event = mmailer .EventDeferred
165184 }
166-
167185 h .MessageId = e .Message .Headers .MessageID
168186 h .Email = e .Recipient
169- h .Info = fmt .Sprintf ("%s; %d; %s" , e .Reason , e .DeliveryStatus .Code , e .DeliveryStatus .Description )
187+ h .Info = infoString (true , e .Reason , e .Severity , e .DeliveryStatus )
188+
170189 default :
171190 logger .Warn (fmt .Sprintf ("received unsupported webhook event: %s" , h .Event ))
172191 return nil , nil
@@ -175,12 +194,62 @@ func (m *Mailgun) UnmarshalPosthook(body []byte) ([]mmailer.Posthook, error) {
175194 return []mmailer.Posthook {h }, nil
176195}
177196
197+ func infoString (fail bool , reason , severity string , st events.DeliveryStatus ) string {
198+ latency := time .Duration (st .SessionSeconds * float64 (time .Second )).Truncate (time .Millisecond )
199+
200+ var words []string
201+ if st .Code != 0 {
202+ words = append (words , fmt .Sprintf ("%d" , st .Code ))
203+ }
204+ if st .EnhancedCode != "" {
205+ words = append (words , st .EnhancedCode )
206+ }
207+ if ! fail {
208+ words = append (words , st .Message )
209+ }
210+ if st .MxHost != "" {
211+ words = append (words , st .MxHost )
212+ }
213+ if latency > time .Millisecond {
214+ words = append (words , latency .String ())
215+ }
216+ if reason != "" {
217+ words = append (words , reason )
218+ }
219+ if severity != "" {
220+ words = append (words , severity )
221+ }
222+
223+ var flags []string
224+ if st .AttemptNo > 0 {
225+ flags = append (flags , fmt .Sprintf ("attempt:%d" , st .AttemptNo ))
226+ }
227+ if st .Utf8 != nil && * st .Utf8 {
228+ flags = append (flags , "utf8" )
229+ }
230+ if st .TLS != nil && * st .TLS {
231+ flags = append (flags , "tls" )
232+ }
233+ if st .CertificateVerified != nil && * st .CertificateVerified {
234+ flags = append (flags , "certificate-verified" )
235+ }
236+ if len (flags ) > 0 {
237+ words = append (words , fmt .Sprintf ("[%s]" , strings .Join (flags , ", " )))
238+ }
239+
240+ msg := strings .Join (words , " " )
241+ if fail {
242+ msg += ": " + st .Message
243+ }
244+ return msg
245+ }
246+
178247type configurer struct {}
179248
180249func (s configurer ) SetIpPool (poolId string , message * mailgun.PlainMessage ) {
181250 // TODO?
182251}
183252
184253func (s configurer ) DisableTracking (message * mailgun.PlainMessage ) {
185- message .SetTracking (false )
254+ message .SetTracking (false ) // untested
186255}
0 commit comments