|
|
@@ -56,66 +56,118 @@ function makeMailchimpProvider() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async function unsubscribe(user) {
|
|
|
+ async function unsubscribe(user, options = {}) {
|
|
|
try {
|
|
|
const path = getSubscriberPath(user.email)
|
|
|
- await mailchimp.put(path, {
|
|
|
- email_address: user.email,
|
|
|
- status: 'unsubscribed',
|
|
|
- status_if_new: 'unsubscribed',
|
|
|
- merge_fields: getMergeFields(user)
|
|
|
- })
|
|
|
- logger.info({ user }, 'finished unsubscribing user from newsletter')
|
|
|
+ if (options.delete) {
|
|
|
+ await mailchimp.delete(path)
|
|
|
+ } else {
|
|
|
+ await mailchimp.patch(path, {
|
|
|
+ status: 'unsubscribed',
|
|
|
+ merge_fields: getMergeFields(user)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ logger.info(
|
|
|
+ { user, options },
|
|
|
+ 'finished unsubscribing user from newsletter'
|
|
|
+ )
|
|
|
} catch (err) {
|
|
|
+ if (err.status === 404 || err.status === 405) {
|
|
|
+ // silently ignore users who were never subscribed (404) or previously deleted (405)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
if (err.message.includes('looks fake or invalid')) {
|
|
|
logger.info(
|
|
|
- { err, user },
|
|
|
+ { err, user, options },
|
|
|
'Mailchimp declined to unsubscribe user because it finds the email looks fake'
|
|
|
)
|
|
|
- } else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ throw new OError({
|
|
|
+ message: 'error unsubscribing user from newsletter',
|
|
|
+ info: { userId: user._id }
|
|
|
+ }).withCause(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async function changeEmail(user, newEmail) {
|
|
|
+ const oldEmail = user.email
|
|
|
+
|
|
|
+ try {
|
|
|
+ await updateEmailInMailchimp(user, newEmail)
|
|
|
+ } catch (updateError) {
|
|
|
+ // if we failed to update the user, delete their old email address so that
|
|
|
+ // we don't leave it stuck in mailchimp
|
|
|
+ logger.info(
|
|
|
+ { oldEmail, newEmail, updateError },
|
|
|
+ 'unable to change email in newsletter, removing old mail'
|
|
|
+ )
|
|
|
+
|
|
|
+ try {
|
|
|
+ await unsubscribe(user, { delete: true })
|
|
|
+ } catch (unsubscribeError) {
|
|
|
+ // something went wrong removing the user's address
|
|
|
throw new OError({
|
|
|
- message: 'error unsubscribing user from newsletter',
|
|
|
- info: { userId: user._id }
|
|
|
- }).withCause(err)
|
|
|
+ message:
|
|
|
+ 'error unsubscribing old email in response to email change failure',
|
|
|
+ info: { oldEmail, newEmail, updateError }
|
|
|
+ }).withCause(unsubscribeError)
|
|
|
+ }
|
|
|
+
|
|
|
+ // throw the error, unless it was an expected one that we can ignore
|
|
|
+ if (!updateError.info || !updateError.info.nonFatal) {
|
|
|
+ throw updateError
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async function changeEmail(oldEmail, newEmail) {
|
|
|
+ async function updateEmailInMailchimp(user, newEmail) {
|
|
|
+ const oldEmail = user.email
|
|
|
+
|
|
|
+ // mailchimp doesn't give us error codes, so we have to parse the message :'(
|
|
|
+ const errors = {
|
|
|
+ 'merge fields were invalid': 'user has never subscribed',
|
|
|
+ 'could not be validated':
|
|
|
+ 'user has previously unsubscribed or new email already exist on list',
|
|
|
+ 'is already a list member': 'new email is already on mailing list',
|
|
|
+ 'looks fake or invalid': 'mail looks fake to mailchimp'
|
|
|
+ }
|
|
|
+
|
|
|
try {
|
|
|
const path = getSubscriberPath(oldEmail)
|
|
|
- await mailchimp.put(path, {
|
|
|
+ await mailchimp.patch(path, {
|
|
|
email_address: newEmail,
|
|
|
- status_if_new: 'unsubscribed'
|
|
|
+ merge_fields: getMergeFields(user)
|
|
|
})
|
|
|
logger.info('finished changing email in the newsletter')
|
|
|
} catch (err) {
|
|
|
- if (err.message.includes('merge fields were invalid')) {
|
|
|
- logger.info(
|
|
|
- { oldEmail, newEmail },
|
|
|
- 'unable to change email in newsletter, user has never subscribed'
|
|
|
- )
|
|
|
- } else if (err.message.includes('could not be validated')) {
|
|
|
- logger.info(
|
|
|
- { oldEmail, newEmail },
|
|
|
- 'unable to change email in newsletter, user has previously unsubscribed or new email already exist on list'
|
|
|
- )
|
|
|
- } else if (err.message.includes('is already a list member')) {
|
|
|
- logger.info(
|
|
|
- { oldEmail, newEmail },
|
|
|
- 'unable to change email in newsletter, new email is already on mailing list'
|
|
|
- )
|
|
|
- } else if (err.message.includes('looks fake or invalid')) {
|
|
|
- logger.info(
|
|
|
- { oldEmail, newEmail },
|
|
|
- 'unable to change email in newsletter, email looks fake to mailchimp'
|
|
|
- )
|
|
|
- } else {
|
|
|
- throw new OError({
|
|
|
- message: 'error changing email in newsletter',
|
|
|
- info: { oldEmail, newEmail }
|
|
|
- }).withCause(err)
|
|
|
+ // silently ignore users who were never subscribed
|
|
|
+ if (err.status === 404) {
|
|
|
+ return
|
|
|
}
|
|
|
+
|
|
|
+ // look through expected mailchimp errors and log if we find one
|
|
|
+ Object.keys(errors).forEach(key => {
|
|
|
+ if (err.message.includes(key)) {
|
|
|
+ const message = `unable to change email in newsletter, ${errors[key]}`
|
|
|
+
|
|
|
+ logger.info({ oldEmail, newEmail }, message)
|
|
|
+
|
|
|
+ // throw a non-fatal error
|
|
|
+ throw new OError({
|
|
|
+ message: message,
|
|
|
+ info: { oldEmail, newEmail, nonFatal: true }
|
|
|
+ }).withCause(err)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // if we didn't find an expected error, generate something to throw
|
|
|
+ throw new OError({
|
|
|
+ message: 'error changing email in newsletter',
|
|
|
+ info: { oldEmail, newEmail }
|
|
|
+ }).withCause(err)
|
|
|
}
|
|
|
}
|
|
|
|