Problem/Motivation
1) User interface
While working on issue #2348119 , it was noticed that the core MailManager->mail() , when encountering a problem with passing the mail on to the mail server, logs the error and then uses drupal_set_message() to tell the visitor about it.
It already returns an array that contains the outcome of the attempt, which can be then handled by the caller.
This causes problems for modules that want to provide better errors to the user ( e.g. in the case of the contact module, which mail failed instead of all of them individually ).
The first idea was to extend the class when wanting to override this behavior, but that leads to duplicating the entire function minus a line.
2) Running from cron
A common setup is to use crontab to schedule drush cron via drush. If drush detects any error messages from messenger()->addError, prints them and returns an error to the shell. Depending on the exact config, this might trigger cron to send an emergency mail. So a single email failing to send is being given the same level of urgency as a major problem such as the overnight backup failing.
Simplenews module sends emails in bulk to newsletter subscribers using cron. There appears to be no way for the module to avoid this unwarranted highly-visible false alarm without a change to core.
Further context
For the default PHP mailer, a successful result means "nothing more than the message being accepted at php-level, which still doesn't guarantee it to be delivered". Hence an error result typically indicates a fairly serious general problem.
Alternative mail-sending plug-in implementations can return an error much more easily. In particular Swiftmailer sending over SMTP does so for a bad email address. On a busy site with lots of subscribers, such errors are likely to occur daily.
Proposed resolution
Specify that a parameter with key '_error_message' has the special meaning to specify translatable markup to use as an error message if needed.
Remaining tasks
Write a CR
- calling code can supply an error message
- MailManager implementations should follow the new param
User interface changes
User will see less (and hopefully more targeted and specific to the action at hand) messages.
API changes
Specify that a parameter with key '_error_message' has the special meaning to specify translatable markup to use as an error message if needed.
| Comment | File | Size | Author |
|---|---|---|---|
| #41 | mail-error.2349725-interdiff-39-41.txt | 1.66 KB | adamps |
| #41 | mail-error.2349725-41.patch | 3.95 KB | adamps |
| #39 | mail-error.2349725-interdiff-37-39.txt | 1.59 KB | adamps |
| #39 | mail-error.2349725-39.patch | 3.89 KB | adamps |
| #37 | mail-error.2349725-interdiff-35-37.txt | 1.41 KB | adamps |
Comments
Comment #2
hexblot commentedInteresting, there is a test in the User module requiring this exact text to be present in order to simulate a failed welcome email sending.
However, this seems to be the only point where this was expected to be present, so we can either:
* patch the User module and test so that when a failed mail attempt is made, the User module handles the failure gracefully (instead of relying on the core Mail module to throw an error) and testing for that, or
* patch the test to not require this text (it also asserts whether the user is told that a message was sent)
* find a way to make this message optional
Comment #3
larowlan+1 for it being the callers job
Comment #4
andypostSo now the mail hanlder just need to check result of
$this->mailManager->mail()Comment #5
alexpottGiven the point of the release we're at we should be handling this in the most non-disruptive way possible. It should be possible to disable the message and have the calling code handle it but the default should be the set the message. We could even deprecate this for Drupal 9 and say that we going to remove this option in Drupal 9 and make it 100% the callers responsibility.
Comment #7
badrange commentedHow far is this from being ready to be committed? Does look like a beneficial addition in many use cases.
Comment #12
alexpottHere's a patch. One thought is should we deprecate the behaviour and thereby not use it in core?
Comment #16
adamps commentedNew patch reroll and fixes tests
Comment #17
adamps commentedAdding an argument to
mail()affects derived classes that override the function and will not have the extra argument. In particular, this applies to MailsystemManager in the popular Mail System module.I'm not an expert, but my guess that this change is allowed by the BC rules. However we should have a CR to flag the impact on other modules.
Comment #18
adamps commented1) It seems like a good idea to deprecate the message from this function to allow the caller to make the correct presentation rather than printing a generic one.
But how will deprecation work in the current patch? Shall we trigger a deprecation warning if
$display_error = TRUE? This forces callers to fill in all intermediate optional parameters so it's not a simple edit. Then in D9 we remove$display_errorfrom the interface and callers remove all the parameters they just added? However any unchanged calling code runs in D9 just the message is missing. It doesn't seem that neat!Alternative idea: create a new method
MailsystemManager::sendMailwith identical signature tomail, however it doesn't output a message. Deprecatemailand remove it in D9. Now calling code has a simple change now from mail to sendMail and no need to change again in D9. Unchanged calling code will fail in D9.2) I wonder if we should also remove the generic log? Again it might not be appropriate.
==
Setting "Contributed project blocker" for simplenews module.
Comment #19
adamps commentedComment #20
adamps commentedUpdate on #18
After thinking some more, I think it's best to defer the deprecation until D9. If we try to do it now, we need to alter all calls to
MailManager->mail()in core to set $display_error = FALSE (filling in all intermediate optional parameters) and then remove all of that code again in D9. I raised #3053749: Show specific/appropriate messages for mail errors.In terms of the log, let's leave that to a separate issue, I don't think it's quite the same situation.
Updated patch adds a @todo for the deprecation.
Comment #21
jonathanshawI agree: the deprecation is tricky whereas the fix here is easy.
Comment #22
berdirThis is definitely an API change, we can not add new arguments to an existing method, and removing it would be an API change again.
We have a $params array that is typically used to pass stuff through to hook_mail(), so we could easily define a special key foy like '_display_error' that we'd use that for, that's also easy to remove again.
Another option would be to not leave BC to the caller but make it a site-wide thing with a setting, we've done similar things before e.g. in the serialization module with returning (or not) values in the correct type. Introduce a setting, false by default, set it to TRUE in an update function and a requirements hook.
Or last, say that displaying a message or not is not actually covered by BC policy, which I think would be fair, but we'd need buy-in from a maintainer, not sure if framework or release :) It's certainly less of an API change than the current patch. Either way, we should actually revisit why this was originally added, which was a long time ago in #169627: Proper e-mail errors logging apparently. On example could be a site that requires approval of registrations, and if the admin wouldn't get that message, he'd not know about the new registration, so we tell the user that he might want to contact the admin directly.. So maybe we should somehow keep that behavior for specific use cases and display a context-specific message?
Comment #23
jonathanshawIt's the change to the interface that would break contrib implementations of MailManager right? I'd forgotten that, I thought we were safe because the new parameter was optional.
That seems a good solution.
What @larowlan said in #3 makes sense to me: ideally (leaving aside BC) in the future error displaying should be the responsibility of the calling context, not MailManager.
The convenience to callers of handling error display inside MailManager seems too small to be worth the complexity.
Comment #24
berdir> What @larowlan said in #3 makes sense to me: ideally (leaving aside BC) in the future error displaying should be the responsibility of the calling context, not MailManager.
Absolutely. But it's still *our* responsibility for the calls that are happening in Drupal core, so if we remove it by default, we need to think about which callers need to do something about it now. That's what I mean.
Comment #25
berdirOf course, as long as core itself still displays the error that would be OK, so in that regard, the current patch would be OK. But the policy that we have in Drupal core is that we are not allowed to rely on deprecated code usages/behaviors. But the more I think about it, we should IMHO either not have BC at all for the message or do it with a global flag or so. If we have a flag, then by our policy, we would need to do a @trigger_error() to make every caller aware that he is relying on a deprecated before, only then to invert it and do a @trigger_error() in 9.x for anyone passing the no longer existing key, so forcing anyone using mail() to change their code twice.
Comment #26
jonathanshawWhat do you think of Adam's idea of deprecating mail() and replacing it with a new method sendMail()? This way we preserve BC but everyone using mail() only has to change their code once.
Comment #27
berdirBC and the interface goes in both directions. Adding a new method would also break contrib implementations unless it would internally call the same method again and set a flag or something.
I still think the easiest option is to just remove it or maybe have a setting but IMHO that's overkill.
Comment #28
adamps commentedThanks @Berdir
So we remove the internal error message in
MailManagerInterface->mail()and add messages to all calling code in core. I have updated the IS.I don't see any advantage to a global setting to control the internal error message. Calling code in core will print error messages. Calling code in contrib maybe some prints and some doesn't. So if you have the setting on, then you definitely get duplicates and if you have it off you maybe get some missing messages.
Comment #29
adamps commentedI'm not convinced we can remove the message in a minor release and get an acceptable result. Suppose we update calling code in contrib to print a message. Then if someone is still running against the old core, there will be a double message. Or if they upgrade core before contrib there will be no message.
@Berdir #22
Yes so there are lots of complications to worry about.
@Berdir #22
This seems like a good idea - why don't we do that? Then it can be almost like the existing patch in #20. We can have the same deprecation strategy - to alter the default in D9 and change core callers at the same time.
Comment #30
berdirWell, one argument against doing that is what I wrote in #25:
> If we have a flag, then by our policy, we would need to do a @trigger_error() to make every caller aware that he is relying on a deprecated before, only then to invert it and do a @trigger_error() in 9.x for anyone passing the no longer existing key, so forcing anyone using mail() to change their code twice.
If we do deprecate it, then core must not in any way rely on it, and we have to basically change it twice and trigger deprecations twice.
One workaround for the double message thing would be to display the identical message, then drupal automatically deduplicates them.
I'm not sure what's best, but I think it would make sense to try and get some input from one of the core maintainers on this, we could bring it up in #d9readiness for example.
Comment #31
jonathanshawComment #32
adamps commentedSo let's drop the idea of deprecation:
Instead this issue can keep the message by default but add a flag to disable it.
However even then it's tricky. If contrib code sets the flag to request no error, how does it know if the request was followed? There might still be an error displayed if running D8.7 core or for an alternate mail manager implementation that has not implemented the flag.
So instead I propose we add a parameter
$params['_error_message']which is the alternate preferred error message in case of error. If this is an empty string then the error is skipped. If the parameter is missing, then the default error message is used. If new contrib is running against old core then the parameter is ignored, but you still get precisely one error message, so I think it's good.Comment #33
jonathanshawWhat you're saying is basically "Permanently keep the current situation of displaying errors as the default permanently, just add a param flag to allow overriding that behavior".
Seems like a good compromise to me.
Comment #34
adamps commentedBasically yes. But nothing is "permanent" - this issue is keeping the current situation of displaying errors as the default. Anyone who wants to tackle the challenge of changing the default is free to do so in a separate issue.
Comment #35
adamps commentedComment #36
alexpottThe comment is not flowing correctly. The first line can go up to 80 characters. Also I don't think we should say string here. Ideally we want people to pass translatable markup in.
Comment #37
adamps commentedThanks @alexpott
I updated the IS to match latest status. I removed "Needs framework manager review" because that was for the tricky BC that we have now avoided.
If this gets to RTBC then I will write the CR.
Comment #38
alexpottWe can still join up the documentation. I.e. there is no need for the line break in between
email.andThe keyComment #39
adamps commentedAh sorry I see now.
Comment #40
jonathanshawPerhaps
Use the key '_error_message' to provide translatable markup to display as a message if an error occurs, or set this to false to disable error display.Comment #41
adamps commentedComment #43
jonathanshawFail is a random: #3055648: Frequent random fail in \Drupal\Tests\media_library\FunctionalJavascript\MediaLibraryTest
RTBC except for Needs CR.
Comment #44
adamps commentedCR ready for review.
Comment #45
jonathanshawComment #46
alexpottCommitted 5614f82 and pushed to 8.8.x. Thanks!
Not backporting to 8.7.x because this requires implementations to change to support the new array key.
Comment #48
adamps commentedGreat thanks @alexpott