I'm writing tests for an application that uses the django-post_office package for most of its email functionality.
The default django.core.mail library contains plenty of useful tools for testing whether or not there are actually emails being sent. (Without actually sending any during the tests)
class TestFunctionThatSendsEmail(Testcase):
#override_settings(
EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend'
)
def test_function_sends_email(self):
self.assertEqual(len(outbox), 0)
run_function_that_calls_email()
self.assertEqual(len(outbox), 1)
...
# other tests
However, the emails in our function are being sent with the django-post_office mail.send() function
# priority now to make sure that they are being sent right away
mail.send(
sender=sender,
recipients=to,
context=context,
template=template_name,
priority='now',
)
Which causes the test above to fail as for some reason the emails do not end up in the outbox.
The strange thing is that if I change the EMAIL_BACKEND to django.core.mail.backends.console.EmailBackend the emails do show up in my terminal, so it is listening to the EMAIL_BACKEND settings.
I've tried finding alternative methods / functions to test this functionality in the django-post_office github, but all I could find was the advice to check to see if the emails are saved to the database and verify their status. (Which I did and works) but the fact that django seems to be unable to detect any emails actually being sent is making me a little bit nervous.
Does anyone know of a way to make emails sent by django-post_office appear in the outbox or, if that is not possible, a way to make sure that they are actually being sent? (beyond checking the database)
The issue is that django-post_office stores the mail backend in the Email object:
class Email(models.Model):
backend_alias = models.CharField(_("Backend alias"), blank=True, default='',
max_length=64)
When dispatch() is called, it uses this backend.
When creating an email, DPO overrides create() to set the backend from def get_available_backends(), which looks up the backend in the settings conf.
This means that using #override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend') won't set the backend_alias in the Email object correctly.
Instead you need to do this manually when you create the object, as per the dispatch test:
def test_dispatch(self):
"""
Ensure that email.dispatch() actually sends out the email
"""
email = Email.objects.create(to=['to#example.com'], from_email='from#example.com',
subject='Test dispatch', message='Message', backend_alias='locmem')
email.dispatch()
self.assertEqual(mail.outbox[0].subject, 'Test dispatch')
If you are using mail.send() you can simply pass the mail.send(backend='locmem') as an argument; make sure you have locmem': 'django.core.mail.backends.locmem.EmailBackend' in your POST_OFFICE settings
I was having the same issue as you described and fixed it by setting the DEFAULT_PRIORITY to 'now':
class TestFunctionThatSendsEmail(Testcase):
#override_settings(
POST_OFFICE={
'BACKENDS': {'default': 'django.core.mail.backends.locmem.EmailBackend'},
'DEFAULT_PRIORITY': 'now',
}
)
def test_function_sends_email(self):
self.assertEqual(len(outbox), 0)
run_function_that_calls_email()
self.assertEqual(len(outbox), 1)
...
# other tests
Here is my properties in settings.py file:
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'infobot9#gmail.com'
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
EMAIL_PORT = 587
and here is my send email method:
from django.core.mail import send_mail
def sendConfirmEmail(email, instance, code):
mail_subject = 'Confirmation code {}'.format(code)
message = render_to_string("msg.html", {
'user': instance,
'code': code
})
to_email = email
send_mail(mail_subject, message, 'infobot9#gmail.com', [to_email],
fail_silently=False)
My Django email sending methods work fine in my local host. After deploying it to Heroku I have allowed the login from unknown devices in my Gmail settings. Gmail does not allow the server login to my account and sends me a message:
spicious login attempt blocked
infobot9#gmail.com
Someone tried to log into your account using the password set for them. If it was not you, we recommend that you change your password as soon as possible.
Unknown Device
April 4, 11:39
Near this place: Dublin, Ireland
176.34.163.6 (IP address)
Should I set extra parameters in my settings.py file or I need change my Gmail account settings?
I urge you not to use Gmail for sending email in production. It's not designed for that, and as you've discovered there are measures in place to prevent it from being used as a spam relay. Even if you're sending legitimate email, Gmail is going to make things difficult for you.
Instead, use a service that's designed to send mail from hosted applications like SendGrid or Mailgun. These are both listed among Heroku's addons and both have free starter plans. Pick one and go through its getting sarted guide. Not only will this work better with small volumes of mail, it sets you up nicely for growth.
Allow less secure apps
Display Unlock Captcha
If you still want to use Gmail, #Pierre Monico's answer will work. I just wanted to make an edit. After allowing less secure apps to sign in to your account and Display Unlock Capatcha you should still keep two things in mind. First be sure to be logged into your browser through the account which you are using in your app to send email so that Capatch should be unlocked for that particular account and the second thing is that Google only allows Display Unlock Capatcha for Only 10 minutes . Thus if you want to use it again and again just keep Display Unlock Capatcha page open in your browser and keep it refreshing after sometime.
Also if you have 2 factor authentication enabled then these steps won't work. Those accounts have different procedure.
Above mentioned answers didn't work for me. So here's how I did it. Basically, you need to configure an app password.
Go to your Gmail account
Security > in 'Signing in to Google' section
Turn on 2-Step Verification
Set app password
and finally, need to configure settings.py or .env file(follow's the env),
EMAIL_HOST_USER=your_email#gmail.com
EMAIL_HOST_PASSWORD=generated_app_password
Tip
Using python-decouple makes it much easier to handle .env data
def send_one_mail(request):
send_mail('The subject', 'The body to testing', 'userone#example.com',['usertwo#gmail.com'], fail_silently=False)
return HttpResponse("This mail has sent successfull")
And my settings.py is
EMAIL_USE_TLS = True
EMAIL_HOST = 'mail.example.com'
EMAIL_HOST_USER = 'userone#example.com'
EMAIL_HOST_PASSWORD = 'password'
EMAIL_PORT = 587
DEFAULT_FROM_EMAIL = 'userone#example.com'
SERVER_EMAIL = 'userone#example.com'
I have a view (which works) as shown above, here userone#example.com is sender and usertwo#gmail.com
is the receiver.
How can i check and act according to errors in sending mail?
(I want to show "Some error occured" in such errors)
How can i determine if the usertwo#gmail.com(reciver) exists or not?
(if email can't be reached, i want to show the user that the email doesn't exist)
I am using postfix,dovecot etc. in server.
You can pick up some errors when you use fail_silently = False. Just wrap send_mail in try/except.
When i wanted more control over e-mail sending, then i stopped using django mailing completely and installed lamson instead (lamsonproject.org). You can basically create your own mail server with it, attach your django orm to it and provide detailed feedback about what has happened to your e-mails. If you insert some kind of downloadable content into those e-mails (like images), then you can even give hashes to images and verify this way if e-mail has been opened too. You could do that with django based email sending too. Lamson just gives bit more control over the what and how that goes on after you hit send button.
django-mail-queue
Anyway, i finally ended-up with using the below package.
pip install django-mail-queue
And here is the docs
https://django-mail-queue.readthedocs.org/en/latest/
According to the documentation, if DEBUG is set to False and something is provided under the ADMINS setting, Django will send an email whenever the code raises a 500 status code. I have the email settings filled out properly (as I can use send_mail fine) but whenever I intentionally put up erroneous code I get my 500.html template but no error email is sent. What could cause Django to not do this?
In my case the cause was missing SERVER_EMAIL setting.
The default for SERVER_EMAIL is root#localhost. But many of email servers including
my email provider do not accept emails from such suspicious addresses. They silently drop the emails.
Changing the sender email address to django#my-domain.example solved the problem. In settings.py:
SERVER_EMAIL = 'django#my-domain.example'
Another possibility for error is trouble with your ADMINS setting. The following setting will cause the sending of mail to admins to fail quietly:
ADMINS = (
('your name', 'me#mydomain.example')
)
What's wrong with that? Well ADMINS needs to be a tuple of tuples, so the above needs to be formatted as
ADMINS = (
('your name', 'me#mydomain.example'),
)
Note the trailing comma. Without the failing comma, the 'to' address on the email will be incorrectly formatted (and then probably discarded silently by your SMTP server).
I had the same situation. I created a new project and app and it worked, so I knew it was my code. I tracked it down to the LOGGING dictionary in settings.py. I had made some changes a few weeks back for logging with Sentry, but for some reason the error just started today. I changed back to the original and got it working:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
Then, I made some changes slowly and got it working with Sentry and emailing the ADMINS as well.
Additionally, the LOGGING configuration gets merged with DEFAULT_LOGGING by default, so it's useful to have a look at the source code of django.utils.log.DEFAULT_LOGGING to understand what else may have an effect on your particular situation.
Make sure your EMAIL_HOST and EMAIL_PORT are set up right in settings.py (these refer to your SMTP server). It might be assuming that you have an SMTP server running on localhost.
To test this locally, run Python's built-in test SMTP server:
python -m smtpd -n -c DebuggingServer localhost:1025
Then set these values in your settings.py
EMAIL_HOST='localhost'
EMAIL_PORT=1025
Trigger a 500 error, and you should see the e-mail appear in the python smtpd terminal window.
My web hosting provider - Webfaction - only allows emails to be sent From an email that has been explicitly created in the administrator panel. Creating one fixed the problem.
Another thing worth noting here is that settings handler500 might bypass the mechanism that sends errors on a 500 if the response from the view doesn't have a status code of 500.
If you have a handler500 set, then in that view respond with something like this.
t = loader.get_template('500.html')
response = HttpResponseServerError(
t.render(RequestContext(request, {'custom_context_var':
'IT BROKE OMG FIRE EVERYONE'})))
response.status_code = 500
return response
Sorry if it is too naive, but in my case the emails were sent but were going directly to the SPAM folder. Before trying more complicated things check your SPAM folder first.
If, for some reason, you set DEBUG_PROPAGATE_EXCEPTIONS to True (it's False by default), email to admin will not work.
Just had the same issue after upgraded to Django 2.1 from Django 1.11. Apparently the ADMINS sections in settings.py has a change. It takes a list of tuples now, rather than the old tuple of tuples. This fixed for me.
##### old #####
ADMINS = (
("Your Name", "your_email#company.example")
)
##### new #####
ADMINS = [
("Your Name", "your_email#company.example")
]
Re: https://docs.djangoproject.com/en/2.1/ref/settings/#admins
Try this
# ./manage shell
>>> from django.core.mail import send_mail
>>> send_mail('Subject here', 'Here is the message.', 'from#example.com',['to#example.com'], fail_silently=False)
With a to#example.com that you actually get email at.
Make sure you have DEBUG = False
This problem annoyed me sufficiently to motivate a post. I provide here the steps I took to resolve this problem (cutting a long story short):
Set-up test page to fail (by re-naming test_template.html)
Check email validations through views for test page in production using send_mail('Hello', 'hello, world', info#xyz.example, [('Name', 'name.name#xyz.example'),], fail_silently=False) where SERVER_EMAIL = info#xyz.example and ADMINS = [('Name', 'name.name#xyz.example'),] in Django settings. In my case, I received the 'hello world' email, but not the Django admin email (which was a pain).
Set-up a simple custom logger to report to a file on the server:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'errors_file': {
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': 'logs/debug.log',
},
},
'loggers': {
'django': {
'handlers': ['errors_file'],
'level': 'ERROR',
'propagate': True,
},
},
}
In my case, navigating to the test page did not generate output in the debug.log file under the logs directory from my project root directory. This indicates that the logger was failing to reach an ERROR 'level'.
Downgrade the threshold for reporting for the custom logger from ERROR to DEBUG. Now, navigating to the test page should deliver some detail. Inspecting this detail revealed in my case that the default 500 page was re-directed (inadvertedly) to an alternative template file called 500.html. This template file made use of a variable for caching, and as the template was not being called through a view that made the variable available in the context, the cache call failed with a missing key reference. Re-naming 500.html solved my problem.
Although it's been a while, here's my response, so that other people can benefit in the future.
In my case, what was preventing emails to be sent to the ADMINS list, when an error occured, was an application specific setting. I was using django-piston, which provides the setting attributes PISTON_EMAIL_ERRORS and PISTON_DISPLAY_ERRORS. Setting these accordingly, enabled the application server to notify my by mail, whenever piston would crash.
... and then there's the facepalm error, when you've used this in development to prevent emails from going out, and then accidentally copy the setting to production:
# Print emails to console
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
(of course you don't see them being printed to console when using a wsgi server). Removing the setting from production fixed this for me.
And yet another thing that can go wrong (I'll just add it to the list, for those people that end up here despite all the great answers above):
Our django setup used SendGrid as the smtp host and had a single admin email-address defined in the django settings. This worked fine for some time, but at some point, mails stopped arriving.
As it turns out, the mail address ended up in the SendGrid 'Bounced' list for some unknown reason, causing emails to that address to be silently dropped forever after. Removing the address from that list, and whitelisting it, fixed the issue.
If you are using or would want to use SendGrid, use the settings below in production.
Install the package
pip install sendgrid-django
Add these settings in settings.py(production)
DEBUG = False
EMAIL_BACKEND = "sendgrid_backend.SendgridBackend"
SENDGRID_API_KEY = "That you generate in sendgrid account"
ADMINS = (
("Your Name", "your_email#company.example")
)
While likely not ideal, I have found using Gmail as the SMTP host works just fine. There is a useful guide at nathanostgard.com.
Feel free to post your relevant settings.py sections (including EMAIL_*, SERVER_EMAIL, ADMINS (just take out your real email), MANAGERS, and DEBUG) if you want an extra set of eyes to check for typos!
For what it's worth I had this issue and none of these suggestions worked for me. It turns out that my problem was that SERVER_EMAIL was set to an address that the server (Webfaction) didn't recognise. If this site were hosted on Webfaction (as my other sites are), this wouldn't be a problem, but as this was on a different server, the Webfaction servers not only check the authentication of the email being sent, but also the From: value as well.
In my case, it's the include_html in mail_admins.
When I set include_html to True,the email server reject to send my email because it think that my emails are spam.
Everything works just fine when I set include_html to False.
I had the same problem and it turned out the mail server did not have the domain name of the email address. I was trying to send from a registered one (it was a new site for a different part of the business). I used an email address that was already valid under the old domain in SERVER_EMAIL. That resolved my issue.
The below info is given in https://docs.djangoproject.com/en/2.1/howto/error-reporting/#email-reports
EMAIL_HOST = "email host"
EMAIL_HOST_USER = "Email username"
EMAIL_HOST_PASSWORD = "Email Password"
DEBUG = False
ADMINS = (
("Your Name", "your_email#company.example")
)
In order to send email, Django requires a few settings telling it how
to connect to your mail server. At the very least, you’ll need to
specify EMAIL_HOST and possibly EMAIL_HOST_USER and
EMAIL_HOST_PASSWORD, though other settings may be also required
depending on your mail server’s configuration. Consult the Django
settings documentation for a full list of email-related settings.
The setting for server_email has a default of root#localhost but so many email carriers don't accept email from such email_servers and that's why the admin emails is not receiving the emails.
This first answer from this trend helped me
Sending email from DebuggingServer localhost:1025 not working
Or you change your DEFAULT_FROM_EMAIL to something else.