forked from ory/kratos
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: send emails via http api endpoint instead of smtp (ory#1030)
- Loading branch information
Showing
6 changed files
with
253 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// Copyright © 2022 Ory Corp | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package courier | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"net/http" | ||
|
||
"github.com/pkg/errors" | ||
|
||
"github.com/ory/kratos/request" | ||
) | ||
|
||
type mailerDataModel struct { | ||
Recipient string | ||
TemplateType TemplateType | ||
TemplateData EmailTemplate | ||
} | ||
|
||
type mailerClient struct { | ||
RequestConfig json.RawMessage | ||
} | ||
|
||
func newMailer(ctx context.Context, deps Dependencies) *mailerClient { | ||
return &mailerClient{ | ||
RequestConfig: deps.CourierConfig().CourierMailerRequestConfig(ctx), | ||
} | ||
} | ||
func (c *courier) dispatchMailerEmail(ctx context.Context, msg Message) error { | ||
builder, err := request.NewBuilder(c.mailerClient.RequestConfig, c.deps) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
tmpl, err := c.smtpClient.NewTemplateFromMessage(c.deps, msg) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
td := mailerDataModel{ | ||
Recipient: msg.Recipient, | ||
TemplateType: msg.TemplateType, | ||
TemplateData: tmpl, | ||
} | ||
|
||
req, err := builder.BuildRequest(ctx, td) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
res, err := c.deps.HTTPClient(ctx).Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer res.Body.Close() | ||
|
||
switch res.StatusCode { | ||
case http.StatusOK: | ||
case http.StatusCreated: | ||
default: | ||
return errors.New(http.StatusText(res.StatusCode)) | ||
} | ||
|
||
c.deps.Logger(). | ||
WithField("message_id", msg.ID). | ||
WithField("message_type", msg.Type). | ||
WithField("message_template_type", msg.TemplateType). | ||
WithField("message_subject", msg.Subject). | ||
Debug("Courier sent out mailer.") | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// Copyright © 2022 Ory Corp | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package courier_test | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
"time" | ||
|
||
"github.com/gofrs/uuid" | ||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/ory/kratos/courier/template/email" | ||
"github.com/ory/kratos/driver/config" | ||
"github.com/ory/kratos/internal" | ||
"github.com/ory/x/resilience" | ||
) | ||
|
||
func TestQueueMailerEmail(t *testing.T) { | ||
ctx := context.Background() | ||
|
||
type sendEmailRequestBody struct { | ||
IdentityID string | ||
IdentityEmail string | ||
Recipient string | ||
TemplateType string | ||
To string | ||
RecoveryCode string | ||
RecoveryURL string | ||
VerificationURL string | ||
VerificationCode string | ||
Body string | ||
Subject string | ||
} | ||
|
||
expectedEmail := []*email.TestStubModel{ | ||
{ | ||
To: "[email protected]", | ||
Subject: "test-mailer-subject-1", | ||
Body: "test-mailer-body-1", | ||
}, | ||
{ | ||
To: "[email protected]", | ||
Subject: "test-mailer-subject-2", | ||
Body: "test-mailer-body-2", | ||
}, | ||
} | ||
|
||
actual := make([]sendEmailRequestBody, 0, 2) | ||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
|
||
rb, err := io.ReadAll(r.Body) | ||
require.NoError(t, err) | ||
|
||
var body sendEmailRequestBody | ||
|
||
err = json.Unmarshal(rb, &body) | ||
require.NoError(t, err) | ||
|
||
assert.NotEmpty(t, r.Header["Authorization"]) | ||
assert.Equal(t, "Basic bWU6MTIzNDU=", r.Header["Authorization"][0]) | ||
|
||
actual = append(actual, body) | ||
})) | ||
t.Cleanup(srv.Close) | ||
|
||
requestConfig := fmt.Sprintf(`{ | ||
"url": "%s", | ||
"method": "POST", | ||
"body": "file://./stub/request.config.mailer.jsonnet", | ||
"auth": { | ||
"type": "basic_auth", | ||
"config": { | ||
"user": "me", | ||
"password": "12345" | ||
} | ||
} | ||
}`, srv.URL) | ||
|
||
conf, reg := internal.NewFastRegistryWithMocks(t) | ||
conf.MustSet(ctx, config.ViperKeyCourierMailerEnabled, true) | ||
conf.MustSet(ctx, config.ViperKeyCourierMailerRequestConfig, requestConfig) | ||
conf.MustSet(ctx, config.ViperKeyCourierSMTPURL, "http://foo.url") | ||
reg.Logger().Level = logrus.TraceLevel | ||
|
||
c, err := reg.Courier(ctx) | ||
require.NoError(t, err) | ||
|
||
ctx, cancel := context.WithCancel(ctx) | ||
defer t.Cleanup(cancel) | ||
|
||
for _, message := range expectedEmail { | ||
id, err := c.QueueEmail(ctx, email.NewTestStub(reg, message)) | ||
require.NoError(t, err) | ||
require.NotEqual(t, uuid.Nil, id) | ||
} | ||
|
||
go func() { | ||
require.NoError(t, c.Work(ctx)) | ||
}() | ||
|
||
require.NoError(t, resilience.Retry(reg.Logger(), time.Second, time.Minute*10, func() error { | ||
if len(actual) == len(expectedEmail) { | ||
return nil | ||
} | ||
return errors.New("capacity not reached") | ||
})) | ||
|
||
for i, message := range actual { | ||
expected := expectedEmail[i] | ||
|
||
assert.Equal(t, expected.To, message.To) | ||
assert.Equal(t, expected.Body, message.Body) | ||
assert.Equal(t, expected.Subject, message.Subject) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
function(ctx) { | ||
recipient: ctx.Recipient, | ||
template_type: ctx.TemplateType, | ||
to: if "TemplateData" in ctx && "To" in ctx.TemplateData then ctx.TemplateData.To else null, | ||
recovery_code: if "TemplateData" in ctx && "RecoveryCode" in ctx.TemplateData then ctx.TemplateData.RecoveryCode else null, | ||
recovery_url: if "TemplateData" in ctx && "RecoveryURL" in ctx.TemplateData then ctx.TemplateData.RecoveryURL else null, | ||
verification_url: if "TemplateData" in ctx && "VerificationURL" in ctx.TemplateData then ctx.TemplateData.VerificationURL else null, | ||
verification_code: if "TemplateData" in ctx && "VerificationCode" in ctx.TemplateData then ctx.TemplateData.VerificationCode else null, | ||
subject: if "TemplateData" in ctx && "Subject" in ctx.TemplateData then ctx.TemplateData.Subject else null, | ||
body: if "TemplateData" in ctx && "Body" in ctx.TemplateData then ctx.TemplateData.Body else null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters