diff --git a/index.html b/index.html index af37e2d..e0ba574 100644 --- a/index.html +++ b/index.html @@ -13842,11 +13842,14 @@
Send an email to one or more recipients
. Upon success, this returns the message
that was just sent. This function
-does not catch and swallow thrown exceptions, it will bubble up.
(mu/defn send-message-or-throw! +does not catch and swallow thrown exceptions, it will bubble up. Should prefer to use [[send-email-retrying!]] unless +the caller has its own retry logic. +
(defn send-message-or-throw! {:style/indent 0} - [{:keys [subject recipients message-type message] :as email} :- EmailMessage] + [{:keys [subject recipients message-type message] :as email}] (try - (when-not (email-smtp-host) - (throw (ex-info (tru "SMTP host is not set.") {:cause :smtp-host-not-set}))) - ;; Now send the email - (let [to-type (if (:bcc? email) :bcc :to)] - (send-email! (smtp-settings) - (merge - {:from (if-let [from-name (email-from-name)] - (str from-name " <" (email-from-address) ">") - (email-from-address)) - to-type recipients - :subject subject - :body (case message-type - :attachments message - :text message - :html [{:type "text/html; charset=utf-8" - :content message}])} - (when-let [reply-to (email-reply-to)] - {:reply-to reply-to})))) - (catch Throwable e - (prometheus/inc :metabase-email/message-errors) - (throw e)) - (finally - (prometheus/inc :metabase-email/messages))))
Schema for the response returned by various functions in [[metabase.email]]. Response will be a map with the key + (when-not (email-smtp-host) + (throw (ex-info (tru "SMTP host is not set.") {:cause :smtp-host-not-set}))) + ;; Now send the email + (let [to-type (if (:bcc? email) :bcc :to)] + (send-email! (smtp-settings) + (merge + {:from (if-let [from-name (email-from-name)] + (str from-name " <" (email-from-address) ">") + (email-from-address)) + to-type recipients + :subject subject + :body (case message-type + :attachments message + :text message + :html [{:type "text/html; charset=utf-8" + :content message}])} + (when-let [reply-to (email-reply-to)] + {:reply-to reply-to})))) + (catch Throwable e + (prometheus/inc :metabase-email/message-errors) + (when (not= :smtp-host-not-set (:cause (ex-data e))) + (throw e))) + (finally + (prometheus/inc :metabase-email/messages))))
Like [[send-message-or-throw!]] but retries sending on errors according to the retry settings.
+(mu/defn send-email-retrying! + [email :- EmailMessage] + ((retry/decorate send-message-or-throw!) email))
Schema for the response returned by various functions in [[metabase.email]]. Response will be a map with the key
:metabase.email/error
, which will either be nil
(indicating no error) or an instance of [[java.lang.Throwable]]
with the error.
(def ^:private SMTPStatus @@ -38995,17 +39004,17 @@`:abstract?` (default = false)
either:text
or:html
or:attachments
.(email/send-message! - :subject "[Metabase] Password Reset Request" - :recipients ["cam@metabase.com"] - :message-type :text - :message "How are you today?")
+ {:subject "[Metabase] Password Reset Request" + :recipients ["cam@metabase.com"] + :message-type :text + :message "How are you today?")}Upon success, this returns the
:message
that was just sent. (TODO -- confirm this.) This function will catch and log any exception, returning a [[SMTPStatus]].
(defn send-message! [& {:as msg-args}] (try - (send-message-or-throw! msg-args) + (send-email-retrying! msg-args) (catch Throwable e (log/warn e (trs "Failed to send email")) {::error e})))
Return a sequence of email addresses for all Admin users.
+ {:subject (str (trs "You''re invited to join {0}''s {1}" company (app-name-trs))) + :recipients [(:email invited)] + :message-type :html + :message message-body})))Return a sequence of email addresses for all Admin users.
The first recipient will be the site admin (or oldest admin if unset), which is the address that should be used in
mailto
links (e.g., for the new user to email with any questions).
Format and send an email informing the user how to reset their password.
+ {:subject (str (if google-auth? + (trs "{0} created a {1} account" (:common_name new-user) (app-name-trs)) + (trs "{0} accepted their {1} invite" (:common_name new-user) (app-name-trs)))) + :recipients recipients + :message-type :html + :message (stencil/render-file "metabase/email/user_joined_notification" + (merge (common-context) + {:logoHeader true + :joinedUserName (or (:first_name new-user) (:email new-user)) + :joinedViaSSO google-auth? + :joinedUserEmail (:email new-user) + :joinedDate (t/format "EEEE, MMMM d" (t/zoned-date-time)) ; e.g. "Wednesday, July 13". TODO - is this what we want? + :adminEmail (first recipients) + :joinedUserEditUrl (str (public-settings/site-url) "/admin/people")}))})))Format and send an email informing the user how to reset their password.
(defn send-password-reset-email! [email sso-source password-reset-url is-active?] {:pre [(u/email? email) @@ -39236,10 +39245,10 @@`:abstract?` (default = false)
:adminEmail (public-settings/admin-email) :adminEmailSet (boolean (public-settings/admin-email))}))] (email/send-message! - :subject (trs "[{0}] Password Reset Request" (app-name-trs)) - :recipients [email] - :message-type :html - :message message-body)))
Format and send an email informing the user that this is the first time we've seen a login from this device. Expects + {:subject (trs "[{0}] Password Reset Request" (app-name-trs)) + :recipients [email] + :message-type :html + :message message-body})))
Format and send an email informing the user that this is the first time we've seen a login from this device. Expects
login history information as returned by metabase.models.login-history/human-friendly-infos
.
(mu/defn send-login-from-new-device-email! [{user-id :user_id, :keys [timestamp], :as login-history} :- [:map [:user_id pos-int?]]] @@ -39256,10 +39265,10 @@`:abstract?` (default = false)
message-body (stencil/render-file "metabase/email/login_from_new_device" context)] (email/send-message! - :subject (trs "We''ve Noticed a New {0} Login, {1}" (app-name-trs) (:first-name user-info)) - :recipients [(:email user-info)] - :message-type :html - :message message-body)))
Find emails for users that have an interest in monitoring the database. + {:subject (trs "We''ve Noticed a New {0} Login, {1}" (app-name-trs) (:first-name user-info)) + :recipients [(:email user-info)] + :message-type :html + :message message-body})))
Find emails for users that have an interest in monitoring the database. If oss that means admin users. If ee that also means users with monitoring and details permissions.
(defn- admin-or-ee-monitoring-details-emails @@ -39320,10 +39329,10 @@`:abstract?` (default = false)
(merge (common-context) context))] (when (seq emails) (email/send-message! - :subject (trs "[{0}] Model cache refresh failed for {1}" (app-name-trs) (:name database)) - :recipients (vec emails) - :message-type :html - :message message-body))))
Format and send an email to the system admin following up on the installation.
+ {:subject (trs "[{0}] Model cache refresh failed for {1}" (app-name-trs) (:name database)) + :recipients (vec emails) + :message-type :html + :message message-body}))))Format and send an email to the system admin following up on the installation.
(defn send-follow-up-email! [email] {:pre [(u/email? email)]} @@ -39613,7 +39622,7 @@`:abstract?` (default = false)
[user subject template-path template-context] (future (try - (email/send-message-or-throw! + (email/send-email-retrying! {:recipients [(:email user)] :message-type :html :subject subject @@ -75479,7 +75488,6 @@`:fallback`
(:require [clojure.string :as str] [metabase.api.common :as api] - [metabase.config :as config] [metabase.email :as email] [metabase.email.messages :as messages] [metabase.events :as events] @@ -75490,7 +75498,6 @@`:fallback`
[metabase.models.interface :as mi] [metabase.models.pulse :as pulse :refer [Pulse]] [metabase.models.serialization :as serdes] - [metabase.models.setting :refer [defsetting]] [metabase.public-settings :as public-settings] [metabase.pulse.markdown :as markdown] [metabase.pulse.parameters :as pulse-params] @@ -75502,7 +75509,7 @@`:fallback`
[metabase.server.middleware.session :as mw.session] [metabase.shared.parameters.parameters :as shared.params] [metabase.util :as u] - [metabase.util.i18n :refer [deferred-tru trs tru]] + [metabase.util.i18n :refer [trs tru]] [metabase.util.log :as log] [metabase.util.malli :as mu] [metabase.util.retry :as retry] @@ -75943,72 +75950,14 @@`:fallback`
(defmethod send-notification! :email [emails] (doseq [{:keys [subject recipients message-type message]} emails] - (try - (email/send-message-or-throw! {:subject subject - :recipients recipients - :message-type message-type - :message message - :bcc? (email/bcc-enabled?)}) - (catch ExceptionInfo e - (when (not= :smtp-host-not-set (:cause (ex-data e))) - (throw e))))))
(declare ^:private reconfigure-retrying)
(defsetting notification-retry-max-attempts - (deferred-tru "The maximum number of attempts for delivering a single notification.") - :type :integer - :default 7 - :on-change reconfigure-retrying)
(defsetting notification-retry-initial-interval - (deferred-tru "The initial retry delay in milliseconds when delivering notifications.") - :type :integer - :default 500 - :on-change reconfigure-retrying)
(defsetting notification-retry-multiplier - (deferred-tru "The delay multiplier between attempts to deliver a single notification.") - :type :double - :default 2.0 - :on-change reconfigure-retrying)
(defsetting notification-retry-randomization-factor - (deferred-tru "The randomization factor of the retry delay when delivering notifications.") - :type :double - :default 0.1 - :on-change reconfigure-retrying)
(defsetting notification-retry-max-interval-millis - (deferred-tru "The maximum delay between attempts to deliver a single notification.") - :type :integer - :default 30000 - :on-change reconfigure-retrying)
(defn- retry-configuration [] - (cond-> {:max-attempts (notification-retry-max-attempts) - :initial-interval-millis (notification-retry-initial-interval) - :multiplier (notification-retry-multiplier) - :randomization-factor (notification-retry-randomization-factor) - :max-interval-millis (notification-retry-max-interval-millis)} - (or config/is-dev? config/is-test?) (assoc :max-attempts 1)))
Returns a notification sender wrapping [[send-notifications!]] retrying
-according to retry-configuration
.
(defn- make-retry-state - [] - (let [retry (retry/random-exponential-backoff-retry "send-notification-retry" - (retry-configuration))] - {:retry retry - :sender (retry/decorate send-notification! retry)}))
Stores the current retry state. Updated whenever the notification
-retry settings change.
-It starts with value nil
but is set whenever the settings change or when
-the first call with retry is made. (See #22790 for more details.)
(defonce - ^{:private true - :doc } - retry-state - (atom nil))
(defn- reconfigure-retrying [_old-value _new-value] - (log/info (trs "Reconfiguring notification sender")) - (reset! retry-state (make-retry-state)))
Like [[send-notification!]] but retries sending on errors according -to the retry settings.
+ (email/send-message-or-throw! {:subject subject + :recipients recipients + :message-type message-type + :message message + :bcc? (email/bcc-enabled?)})))Like [[send-notification!]] but retries sending on errors according to the retry settings.
(defn- send-notification-retrying! [& args] - (when-not @retry-state - (compare-and-set! retry-state nil (make-retry-state))) - (apply (:sender @retry-state) args))
(defn- send-notifications! [notifications] (doseq [notification notifications] ;; do a try-catch around each notification so if one fails, we'll still send the other ones for example, an Alert @@ -96936,7 +96885,7 @@Quartz JavaDoc
(let [legacy-pulse (->> (t2/select :model/Pulse :dashboard_id nil :alert_condition nil :archived false) (map #(assoc % :url (urls/legacy-pulse-url (:id %)))))] (doseq [admin (t2/select :model/User :is_superuser true)] - (email/send-message-or-throw! + (email/send-email-retrying! {:recipients [(:email admin)] :message-type :html :subject "[Metabase] Removal of legacy pulses in upcoming Metabase release" @@ -103359,11 +103308,39 @@Quartz JavaDoc
([x] (re-pattern (rx* x))) ([x & more] (rx (into [:and x] more))))))
Support for in-memory, thread-blocking retrying.
(ns metabase.util.retry + (:require [metabase.models.setting :refer [defsetting]] + [metabase.util.i18n :refer [deferred-tru]]) (:import (io.github.resilience4j.core IntervalFunction) (io.github.resilience4j.retry Retry RetryConfig) (java.util.function Predicate)))
(set! *warn-on-reflection* true)
(defsetting retry-max-attempts + (deferred-tru "The maximum number of attempts for an event.") + :type :integer + :default 7)
(defsetting retry-initial-interval + (deferred-tru "The initial retry delay in milliseconds.") + :type :integer + :default 500)
(defsetting retry-multiplier + (deferred-tru "The delay multiplier between attempts.") + :type :double + :default 2.0)
(defsetting retry-randomization-factor + (deferred-tru "The randomization factor of the retry delay.") + :type :double + :default 0.1)
(defsetting retry-max-interval-millis + (deferred-tru "The maximum delay between attempts.") + :type :integer + :default 30000)
(defn- retry-configuration [] + {:max-attempts (retry-max-attempts) + :initial-interval-millis (retry-initial-interval) + :multiplier (retry-multiplier) + :randomization-factor (retry-randomization-factor) + :max-interval-millis (retry-max-interval-millis)})
(defn- make-predicate [f] (reify Predicate (test [_ x] (f x))))
Returns a randomized exponential backoff retry named retry-name
configured according the options in the second parameter.
Returns a function accepting the same arguments as f
but retrying on error
as specified by retry
.
-The calling thread is blocked during the retries.
(defn decorate - [f ^Retry retry] - (fn [& args] - (let [callable (reify Callable (call [_] (apply f args)))] - (.call (Retry/decorateCallable retry callable)))))
Various schemas that are useful throughout the app.
+ ([f] + (decorate f (random-exponential-backoff-retry (str (random-uuid)) (retry-configuration)))) + ([f ^Retry retry] + (fn [& args] + (let [callable (reify Callable (call [_] (apply f args)))] + (.call (Retry/decorateCallable retry callable))))))Various schemas that are useful throughout the app.
Schemas defined are deprecated and should be replaced with Malli schema defined in [[metabase.util.malli.schema]]. If you update schemas in this ns, please make sure you update the malli schema too. It'll help us makes the transition easier.