Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
174 lines
5.3 KiB
Go
174 lines
5.3 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package courier
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"github.com/ory/kratos/x/events"
|
|
"github.com/ory/x/otelx"
|
|
"github.com/ory/x/otelx/semconv"
|
|
)
|
|
|
|
func (c *courier) channels(ctx context.Context, id string) (Channel, error) {
|
|
cs, err := c.deps.CourierConfig().CourierChannels(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, channel := range cs {
|
|
if channel.ID != id {
|
|
continue
|
|
}
|
|
switch channel.Type {
|
|
case "smtp":
|
|
courierChannel, err := NewSMTPChannelWithCustomTemplates(c.deps, channel.SMTPConfig, c.newEmailTemplateFromMessage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return courierChannel, nil
|
|
case "http":
|
|
return newHttpChannel(channel.ID, &channel.RequestConfig, c.deps), nil
|
|
default:
|
|
return nil, errors.Errorf("unknown courier channel type: %s", channel.Type)
|
|
}
|
|
}
|
|
|
|
return nil, errors.Errorf("no courier channels configured for: %s", id)
|
|
}
|
|
|
|
func (c *courier) DispatchMessage(ctx context.Context, msg Message) (err error) {
|
|
ctx, span := c.deps.Tracer(ctx).Tracer().Start(ctx, "courier.DispatchMessage", trace.WithAttributes(
|
|
attribute.Stringer("message.id", msg.ID),
|
|
attribute.Stringer("message.nid", msg.NID),
|
|
attribute.Stringer("message.type", msg.Type),
|
|
attribute.String("message.template_type", string(msg.TemplateType)),
|
|
attribute.Int("message.send_count", msg.SendCount),
|
|
))
|
|
defer otelx.End(span, &err)
|
|
ctx = semconv.ContextWithAttributes(ctx, semconv.AttrNID(msg.NID))
|
|
|
|
logger := c.deps.Logger().
|
|
WithField("message_id", msg.ID).
|
|
WithField("message_nid", msg.NID).
|
|
WithField("message_type", msg.Type).
|
|
WithField("message_template_type", msg.TemplateType).
|
|
WithField("message_subject", msg.Subject).
|
|
WithField("trace_id", span.SpanContext().TraceID())
|
|
|
|
if err := c.deps.CourierPersister().IncrementMessageSendCount(ctx, msg.ID); err != nil {
|
|
logger.
|
|
WithError(err).
|
|
Error(`Unable to increment the message's "send_count" field`)
|
|
return err
|
|
}
|
|
|
|
channel, err := c.channels(ctx, msg.Channel.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
span.SetAttributes(attribute.String("channel.id", channel.ID()))
|
|
logger = logger.
|
|
WithField("channel", channel.ID())
|
|
|
|
if err := channel.Dispatch(ctx, msg); err != nil {
|
|
return err
|
|
}
|
|
span.AddEvent(events.NewCourierMessageDispatched(ctx, msg.ID, msg.Channel.String(), string(msg.TemplateType)))
|
|
|
|
if err := c.deps.CourierPersister().SetMessageStatus(ctx, msg.ID, MessageStatusSent); err != nil {
|
|
logger.
|
|
WithError(err).
|
|
Error(`Unable to set the message status to "sent".`)
|
|
return err
|
|
}
|
|
|
|
dispatchDuration := time.Since(msg.CreatedAt).Milliseconds()
|
|
logger.WithField("dispatch_duration_ms", dispatchDuration).Debug("Courier sent out message.")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *courier) DispatchQueue(ctx context.Context) (err error) {
|
|
ctx, span := c.deps.Tracer(ctx).Tracer().Start(ctx, "courier.DispatchQueue")
|
|
defer otelx.End(span, &err)
|
|
maxRetries := c.deps.CourierConfig().CourierMessageRetries(ctx)
|
|
pullCount := c.deps.CourierConfig().CourierWorkerPullCount(ctx)
|
|
|
|
//nolint:gosec // disable G115
|
|
messages, err := c.deps.CourierPersister().NextMessages(ctx, uint8(pullCount))
|
|
if err != nil {
|
|
if errors.Is(err, ErrQueueEmpty) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
span.SetAttributes(attribute.Int("messages_count", len(messages)))
|
|
|
|
for k, msg := range messages {
|
|
logger := c.deps.Logger().
|
|
WithField("message_id", msg.ID).
|
|
WithField("message_nid", msg.NID).
|
|
WithField("message_type", msg.Type).
|
|
WithField("message_template_type", msg.TemplateType).
|
|
WithField("message_subject", msg.Subject)
|
|
|
|
if msg.SendCount > maxRetries {
|
|
if err := c.deps.CourierPersister().SetMessageStatus(ctx, msg.ID, MessageStatusAbandoned); err != nil {
|
|
logger.
|
|
WithError(err).
|
|
Error(`Unable to set the retried message's status to "abandoned".`)
|
|
return err
|
|
}
|
|
|
|
msgCtx := semconv.ContextWithAttributes(ctx, semconv.AttrNID(msg.NID))
|
|
span.AddEvent(events.NewCourierMessageAbandoned(msgCtx, msg.ID, msg.Channel.String(), string(msg.TemplateType)))
|
|
|
|
// Skip the message
|
|
logger.
|
|
Warnf(`Message was abandoned because it did not deliver after %d attempts`, msg.SendCount)
|
|
} else if err := c.DispatchMessage(ctx, msg); err != nil {
|
|
logger.
|
|
WithError(err).
|
|
Warn(`Unable to dispatch message.`)
|
|
if err := c.deps.CourierPersister().RecordDispatch(ctx, msg.ID, CourierMessageDispatchStatusFailed, err); err != nil {
|
|
logger.
|
|
WithError(err).
|
|
Error(`Unable to record failure log entry.`)
|
|
if c.failOnDispatchError {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, replace := range messages[k:] {
|
|
if err := c.deps.CourierPersister().SetMessageStatus(ctx, replace.ID, MessageStatusQueued); err != nil {
|
|
logger.
|
|
WithError(err).
|
|
Error(`Unable to reset the failed message's status to "queued".`)
|
|
if c.failOnDispatchError {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.failOnDispatchError {
|
|
return err
|
|
}
|
|
} else if err := c.deps.CourierPersister().RecordDispatch(ctx, msg.ID, CourierMessageDispatchStatusSuccess, nil); err != nil {
|
|
logger.
|
|
WithError(err).
|
|
Error(`Unable to record success log entry.`)
|
|
// continue with execution, as the message was successfully dispatched
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|