amelioration(dolist): ne log erreurs pas les erreurs dans sentry lorsque le contact chez dolist est injoingable ou hardbounce

This commit is contained in:
Martin 2023-04-03 16:25:45 +02:00 committed by mfo
parent a286af8a70
commit 8fa2bbb67d
3 changed files with 73 additions and 11 deletions

View file

@ -4,6 +4,7 @@ class Dolist::API
CONTACT_URL = "https://apiv9.dolist.net/v1/contacts/read?AccountID=%{account_id}"
EMAIL_LOGS_URL = "https://apiv9.dolist.net/v1/statistics/email/sendings/transactional/search?AccountID=%{account_id}"
EMAIL_KEY = 7
STATUS_KEY = 72
DOLIST_WEB_DASHBOARD = "https://campaign.dolist.net/#/%{account_id}/contacts/%{contact_id}/sendings"
EMAIL_MESSAGES_ADRESSES_REPLIES = "https://apiv9.dolist.net/v1/email/messages/addresses/replies?AccountID=%{account_id}"
EMAIL_MESSAGES_ADRESSES_PACKSENDERS = "https://apiv9.dolist.net/v1/email/messages/addresses/packsenders?AccountID=%{account_id}"
@ -13,6 +14,18 @@ class Dolist::API
class_attribute :limit_remaining, :limit_reset_at
# those code are just undocumented
IGNORABLE_API_ERROR_CODE = [
"458",
"402"
]
# see: https://usercampaign.dolist.net/wp-content/uploads/2022/12/Comprendre-les-Opt-out-tableau-v2.pdf
IGNORABLE_CONTACT_STATUSES = [
"4", # Le serveur distant n'accepte pas le mail car il identifie que ladresse e-mail est en erreur.
"7" # Suite à un envoi, le serveur distant accepte le mail dans un premier temps mais envoie une erreur définitive car ladresse e-mail est en erreur. L'adresse e-mail nexiste pas ou n'existe plus.
]
class << self
def save_rate_limit_headers(headers)
self.limit_remaining = headers["X-Rate-Limit-Remaining"].to_i
@ -124,6 +137,31 @@ class Dolist::API
get format_url(EMAIL_MESSAGES_ADRESSES_REPLIES)
end
# Une adresse e-mail peut ne pas être adressable pour différentes raisons (injoignable, plainte pour spam, blocage dun FAI).
# Dans ce cas lAPI denvoi transactionnel renvoie différentes erreurs.
# Pour connaitre exactement le statut dune adresse, je vous invite à récupérer le champ 72 du contact à partir de son adresse e-mail avec la méthode https://api.dolist.com/documentation/index.html#/40e7751d00dc3-rechercher-un-contact
#
# La liste des différents statuts est disponible sur https://usercampaign.dolist.net/wp-content/uploads/2022/12/Comprendre-les-Opt-out-tableau-v2.pdf
def fetch_contact_status(email_address)
url = format(Dolist::API::CONTACT_URL, account_id: account_id)
body = {
Query: {
FieldValueList: [{ ID: 7, Value: email_address }],
OutputFieldIDList: [72]
}
}.to_json
post(url, body)["FieldList"].find { _1['ID'] == 72 }['Value']
end
def ignorable_api_error_code?(api_error_code)
IGNORABLE_API_ERROR_CODE.include?(api_error_code)
end
def ignorable_contact_status?(contact_status)
IGNORABLE_CONTACT_STATUSES.include?(contact_status)
end
private
def format_url(base)

View file

@ -15,12 +15,21 @@ ActiveSupport.on_load(:action_mailer) do
def initialize(mail); end
def deliver!(mail)
response = Dolist::API.new.send_email(mail)
client = Dolist::API.new
response = client.send_email(mail)
if response&.dig("Result")
mail.message_id = response.dig("Result")
else
fail "DoList delivery error. Body: #{response}"
error_code = response&.dig("ResponseStatus", "ErrorCode")
contact_status = if client.ignorable_api_error_code?(error_code)
client.fetch_contact_status(mail.to.first)
else
nil
end
if !client.ignorable_contact_status?(contact_status)
fail "DoList delivery error. Body: #{response}"
end
end
end
end

View file

@ -28,16 +28,31 @@ RSpec.describe ApplicationMailer, type: :mailer do
describe 'dealing with Dolist API error' do
let(:dossier) { create(:dossier, procedure: create(:simple_procedure)) }
before do
ActionMailer::Base.delivery_method = :dolist_api
api_error_response = { "ResponseStatus": { "ErrorCode": "Forbidden", "Message": "Blocked non authorized request", "Errors": [] } }
allow_any_instance_of(Dolist::API).to receive(:send_email).and_return(api_error_response)
end
subject { DossierMailer.with(dossier:).notify_new_draft.deliver_now }
context 'not ignored error' do
before do
ActionMailer::Base.delivery_method = :dolist_api
api_error_response = { "ResponseStatus": { "ErrorCode": "Forbidden", "Message": "Blocked non authorized request", "Errors": [] } }
allow_any_instance_of(Dolist::API).to receive(:send_email).and_return(api_error_response)
end
it 'raise classic error to retry' do
expect { subject }.to raise_error(MailDeliveryError)
expect(EmailEvent.dolist_api.dispatch_error.count).to eq(1)
it 'raise classic error to retry' do
expect { subject }.to raise_error(MailDeliveryError)
expect(EmailEvent.dolist_api.dispatch_error.count).to eq(1)
end
end
context 'ignored error' do
before do
ActionMailer::Base.delivery_method = :dolist_api
api_error_response = { "ResponseStatus" => { "ErrorCode" => "458", "Message" => "The contact is disabled.", "Errors" => [] } }
allow_any_instance_of(Dolist::API).to receive(:send_email).and_return(api_error_response)
allow_any_instance_of(Dolist::API).to receive(:fetch_contact_status).with(dossier.user.email).and_return("7")
end
it 'does not raise' do
expect { subject }.not_to raise_error
end
end
end