GithubHelp home page GithubHelp logo

ui / django-post_office Goto Github PK

View Code? Open in Web Editor NEW
971.0 37.0 270.0 888 KB

A Django app that allows you to send email asynchronously in Django. Supports HTML email, database backed templates and logging.

License: MIT License

Python 98.88% HTML 1.12%

django-post_office's Introduction

Django Post Office

Django Post Office is a simple app to send and manage your emails in Django. Some awesome features are:

  • Designed to scale, handles millions of emails efficiently
  • Allows you to send email asynchronously
  • Multi backend support
  • Supports HTML email
  • Supports inlined images in HTML email
  • Supports database based email templates
  • Supports multilingual email templates (i18n)
  • Built in scheduling support
  • Works well with task queues like RQ or Celery
  • Uses multiprocessing and threading to send a large number of emails in parallel

Dependencies

With this optional dependency, HTML emails are nicely rendered inside the Django admin backend. Without this library, all HTML tags will otherwise be stripped for security reasons.

Installation

Build Status PyPI PyPI version PyPI

pip install django-post_office

Add post_office to your INSTALLED_APPS in django's settings.py:

INSTALLED_APPS = (
    # other apps
    "post_office",
)

Run migrate:

python manage.py migrate

Set post_office.EmailBackend as your EMAIL_BACKEND in Django's settings.py:

EMAIL_BACKEND = 'post_office.EmailBackend'

Quickstart

Send a simple email is really easy:

from post_office import mail

mail.send(
    '[email protected]', # List of email addresses also accepted
    '[email protected]',
    subject='My email',
    message='Hi there!',
    html_message='Hi <strong>there</strong>!',
)

If you want to use templates, ensure that Django's admin interface is enabled. Create an EmailTemplate instance via admin and do the following:

from post_office import mail

mail.send(
    '[email protected]', # List of email addresses also accepted
    '[email protected]',
    template='welcome_email', # Could be an EmailTemplate instance or name
    context={'foo': 'bar'},
)

The above command will put your email on the queue so you can use the command in your webapp without slowing down the request/response cycle too much. To actually send them out, run python manage.py send_queued_mail. You can schedule this management command to run regularly via cron:

* * * * * (/usr/bin/python manage.py send_queued_mail >> send_mail.log 2>&1)

Usage

mail.send()

mail.send is the most important function in this library, it takes these arguments:

Argument Required Description
recipients Yes List of recipient email addresses
sender No Defaults to settings.DEFAULT_FROM_EMAIL, display name like John <[email protected]> is allowed
subject No Email subject (if template is not specified)
message No Email content (if template is not specified)
html_message No HTML content (if template is not specified)
template No EmailTemplate instance or name of template
language No Language in which you want to send the email in (if you have multilingual email templates).
cc No List of emails, will appear in cc field
bcc No List of emails, will appear in bcc field
attachments No Email attachments - a dict where the keys are the filenames and the values are files, file-like-objects or path to file
context No A dict, used to render templated email
headers No A dictionary of extra headers on the message
scheduled_time No A date/datetime object indicating when the email should be sent
expires_at No If specified, mails that are not yet sent won't be delivered after this date.
priority No high, medium, low or now (sent immediately)
backend No Alias of the backend you want to use, default will be used if not specified.
render_on_delivery No Setting this to True causes email to be lazily rendered during delivery. template is required when render_on_delivery is True. With this option, the full email content is never stored in the DB. May result in significant space savings if you're sending many emails using the same template.

Here are a few examples.

If you just want to send out emails without using database templates. You can call the send command without the template argument.

from post_office import mail

mail.send(
    ['[email protected]'],
    '[email protected]',
    subject='Welcome!',
    message='Welcome home, {{ name }}!',
    html_message='Welcome home, <b>{{ name }}</b>!',
    headers={'Reply-to': '[email protected]'},
    scheduled_time=date(2014, 1, 1),
    context={'name': 'Alice'},
)

post_office is also task queue friendly. Passing now as priority into send_mail will deliver the email right away (instead of queuing it), regardless of how many emails you have in your queue:

from post_office import mail

mail.send(
    ['[email protected]'],
    '[email protected]',
    template='welcome_email',
    context={'foo': 'bar'},
    priority='now',
)

This is useful if you already use something like django-rq to send emails asynchronously and only need to store email related activities and logs.

If you want to send an email with attachments:

from django.core.files.base import ContentFile
from post_office import mail

mail.send(
    ['[email protected]'],
    '[email protected]',
    template='welcome_email',
    context={'foo': 'bar'},
    priority='now',
    attachments={
        'attachment1.doc': '/path/to/file/file1.doc',
        'attachment2.txt': ContentFile('file content'),
        'attachment3.txt': {'file': ContentFile('file content'), 'mimetype': 'text/plain'},
    }
)

Template Tags and Variables

post-office supports Django's template tags and variables. For example, if you put Hello, {{ name }} in the subject line and pass in {'name': 'Alice'} as context, you will get Hello, Alice as subject:

from post_office.models import EmailTemplate
from post_office import mail

EmailTemplate.objects.create(
    name='morning_greeting',
    subject='Morning, {{ name|capfirst }}',
    content='Hi {{ name }}, how are you feeling today?',
    html_content='Hi <strong>{{ name }}</strong>, how are you feeling today?',
)

mail.send(
    ['[email protected]'],
    '[email protected]',
    template='morning_greeting',
    context={'name': 'alice'},
)

# This will create an email with the following content:
subject = 'Morning, Alice',
content = 'Hi alice, how are you feeling today?'
content = 'Hi <strong>alice</strong>, how are you feeling today?'

Multilingual Email Templates

You can easily create email templates in various different languages. For example:

template = EmailTemplate.objects.create(
    name='hello',
    subject='Hello world!',
)

# Add an Indonesian version of this template:
indonesian_template = template.translated_templates.create(
    language='id',
    subject='Halo Dunia!'
)

Sending an email using template in a non default language is similarly easy:

mail.send(
    ['[email protected]'],
    '[email protected]',
    template=template, # Sends using the default template
)

mail.send(
    ['[email protected]'],
    '[email protected]',
    template=template,
    language='id', # Sends using Indonesian template
)

Inlined Images

Often one wants to render images inside a template, which are attached as inlined MIMEImage to the outgoing email. This requires a slightly modified Django Template Engine, keeping a list of inlined images, which later will be added to the outgoing message.

First we must add a special Django template backend to our list of template engines:

TEMPLATES = [
    {
        ...
    }, {
        'BACKEND': 'post_office.template.backends.post_office.PostOfficeTemplates',
        'APP_DIRS': True,
        'DIRS': [],
        'OPTIONS': {
            'context_processors': [
                'django.contrib.auth.context_processors.auth',
                'django.template.context_processors.debug',
                'django.template.context_processors.i18n',
                'django.template.context_processors.media',
                'django.template.context_processors.static',
                'django.template.context_processors.tz',
                'django.template.context_processors.request',
            ]
        }
    }
]

then we must tell Post-Office to use this template engine:

POST_OFFICE = {
    'TEMPLATE_ENGINE': 'post_office',
}

In templates used to render HTML for emails add

{% load post_office %}

<p>... somewhere in the body ...</p>
<img src="{% inline_image 'path/to/image.png' %}" />

Here the templatetag named inline_image is used to keep track of inlined images. It takes a single parameter. This can either be the relative path to an image file located in one of the static directories, or the absolute path to an image file, or an image-file object itself. Templates rendered using this templatetag, render a reference ID for each given image, and store these images inside the context of the adopted template engine. Later on, when the rendered template is passed to the mailing library, those images will be transferred to the email message object as MIMEImage-attachments.

To send an email containing both, a plain text body and some HTML with inlined images, use the following code snippet:

from django.core.mail import EmailMultiAlternatives
from django.template.loader import get_template

subject, body = "Hello", "Plain text body"
from_email, to_email = "[email protected]", "[email protected]"
email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
template = get_template('email-template-name.html', using='post_office')
context = {...}
html = template.render(context)
email_message.attach_alternative(html, 'text/html')
template.attach_related(email_message)
email_message.send()

To send an email containing HTML with inlined images, but without a plain text body, use this code snippet:

from django.core.mail import EmailMultiAlternatives
from django.template.loader import get_template

subject, from_email, to_email = "Hello", "[email protected]", "[email protected]"
template = get_template('email-template-name.html', using='post_office')
context = {...}
html = template.render(context)
email_message = EmailMultiAlternatives(subject, html, from_email, [to_email])
email_message.content_subtype = 'html'
template.attach_related(email_message)
email_message.send()

Custom Email Backends

By default, post_office uses django's smtp.EmailBackend. If you want to use a different backend, you can do so by configuring BACKENDS.

For example if you want to use django-ses:

# Put this in settings.py
POST_OFFICE = {
    ...
    'BACKENDS': {
        'default': 'smtp.EmailBackend',
        'ses': 'django_ses.SESBackend',
    }
}

You can then choose what backend you want to use when sending mail:

# If you omit `backend_alias` argument, `default` will be used
mail.send(
    ['[email protected]'],
    '[email protected]',
    subject='Hello',
)

# If you want to send using `ses` backend
mail.send(
    ['[email protected]'],
    '[email protected]',
    subject='Hello',
    backend='ses',
)

Management Commands

  • send_queued_mail - send queued emails, those aren't successfully sent will be marked as failed. Accepts the following arguments:
Argument Description
--processes or -p Number of parallel processes to send email. Defaults to 1
--lockfile or -L Full path to file used as lock file. Defaults to /tmp/post_office.lock
  • cleanup_mail - delete all emails created before an X number of days (defaults to 90).
Argument Description
--days or -d Email older than this argument will be deleted. Defaults to 90
--delete-attachments Flag to delete orphaned attachment records and files on disk. If not specified, attachments won't be deleted.

You may want to set these up via cron to run regularly:

* * * * * (cd $PROJECT; python manage.py send_queued_mail --processes=1 >> $PROJECT/cron_mail.log 2>&1)
0 1 * * * (cd $PROJECT; python manage.py cleanup_mail --days=30 --delete-attachments >> $PROJECT/cron_mail_cleanup.log 2>&1)

Settings

This section outlines all the settings and configurations that you can put in Django's settings.py to fine tune post-office's behavior.

Batch Size

If you may want to limit the number of emails sent in a batch ( useful in a low memory environment), use the BATCH_SIZE argument to limit the number of queued emails fetched in one batch. BATCH_SIZE defaults to 100.

# Put this in settings.py
POST_OFFICE = {
    ...
    'BATCH_SIZE': 100,
}

Version 3.8 introduces a companion setting called BATCH_DELIVERY_TIMEOUT. This setting specifies the maximum time allowed for each batch to be delivered, this is useful to guard against cases where delivery process never terminates. Defaults to 180.

If you send a large number of emails in a single batch on a slow connection, consider increasing this number.

# Put this in settings.py
POST_OFFICE = {
    ...
    'BATCH_DELIVERY_TIMEOUT': 180,
}

Default Priority

The default priority for emails is medium, but this can be altered by setting DEFAULT_PRIORITY. Integration with asynchronous email backends (e.g. based on Celery) becomes trivial when set to now.

# Put this in settings.py
POST_OFFICE = {
    ...
    'DEFAULT_PRIORITY': 'now',
}

Lock File Name

The default lock file name is post_office, but this can be altered by setting LOCK_FILE_NAME in the configuration.

# Put this in settings.py
POST_OFFICE = {
    ...
    'LOCK_FILE_NAME': 'custom_lock_file',
}

Override Recipients

Defaults to None. This option is useful if you want to redirect all emails to specified a few email for development purposes.

# Put this in settings.py
POST_OFFICE = {
    ...
    'OVERRIDE_RECIPIENTS': ['to@example.com', 'to2@example.com'],
}

Message-ID

The SMTP standard requires that each email contains a unique Message-ID. Typically the Message-ID consists of two parts separated by the @ symbol: The left part is a generated pseudo random number. The right part is a constant string, typically denoting the full qualified domain name of the sending server.

By default, Django generates such a Message-ID during email delivery. Since django-post_office keeps track of all delivered emails, it can be very useful to create and store this Message-ID while creating each email in the database. This identifier then can be looked up in the Django admin backend.

To enable this feature, add this to your Post-Office settings:

# Put this in settings.py
POST_OFFICE = {
    ...
    'MESSAGE_ID_ENABLED': True,
}

It can further be fine tuned, using for instance another full qualified domain name:

# Put this in settings.py
POST_OFFICE = {
    ...
    'MESSAGE_ID_ENABLED': True,
    'MESSAGE_ID_FQDN': 'example.com',
}

Otherwise, if MESSAGE_ID_FQDN is unset (the default), django-post_office falls back to the DNS name of the server, which is determined by the network settings of the host.

Retry

Not activated by default. You can automatically requeue failed email deliveries. You can also configure failed deliveries to be retried after a specific time interval.

# Put this in settings.py
POST_OFFICE = {
    ...
    'MAX_RETRIES': 4,
    'RETRY_INTERVAL': datetime.timedelta(minutes=15),  # Schedule to be retried 15 minutes later
}

Log Level

Logs are stored in the database and is browsable via Django admin. The default log level is 2 (logs both successful and failed deliveries) This behavior can be changed by setting LOG_LEVEL.

# Put this in settings.py
POST_OFFICE = {
    ...
    'LOG_LEVEL': 1, # Log only failed deliveries
}

The different options are:

  • 0 logs nothing
  • 1 logs only failed deliveries
  • 2 logs everything (both successful and failed delivery attempts)

Sending Order

The default sending order for emails is -priority, but this can be altered by setting SENDING_ORDER. For example, if you want to send queued emails in FIFO order :

# Put this in settings.py
POST_OFFICE = {
    ...
    'SENDING_ORDER': ['created'],
}

Context Field Serializer

If you need to store complex Python objects for deferred rendering (i.e. setting render_on_delivery=True), you can specify your own context field class to store context variables. For example if you want to use django-picklefield:

# Put this in settings.py
POST_OFFICE = {
    ...
    'CONTEXT_FIELD_CLASS': 'picklefield.fields.PickledObjectField',
}

CONTEXT_FIELD_CLASS defaults to django.db.models.JSONField.

Logging

You can configure post-office's logging from Django's settings.py. For example:

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "post_office": {
            "format": "[%(levelname)s]%(asctime)s PID %(process)d: %(message)s",
            "datefmt": "%d-%m-%Y %H:%M:%S",
        },
    },
    "handlers": {
        "post_office": {
            "level": "DEBUG",
            "class": "logging.StreamHandler",
            "formatter": "post_office"
        },
        # If you use sentry for logging
        'sentry': {
            'level': 'ERROR',
            'class': 'raven.contrib.django.handlers.SentryHandler',
        },
    },
    'loggers': {
        "post_office": {
            "handlers": ["post_office", "sentry"],
            "level": "INFO"
        },
    },
}

Threads

post-office >= 3.0 allows you to use multiple threads to dramatically speed up the speed at which emails are sent. By default, post-office uses 5 threads per process. You can tweak this setting by changing THREADS_PER_PROCESS setting.

This may dramatically increase the speed of bulk email delivery, depending on which email backends you use. In my tests, multi threading speeds up email backends that use HTTP based (REST) delivery mechanisms but doesn't seem to help SMTP based backends.

# Put this in settings.py
POST_OFFICE = {
    ...
    'THREADS_PER_PROCESS': 10,
}

Performance

Caching

if Django's caching mechanism is configured, post_office will cache EmailTemplate instances . If for some reason you want to disable caching, set POST_OFFICE_CACHE to False in settings.py:

## All cache key will be prefixed by post_office:template:
## To turn OFF caching, you need to explicitly set POST_OFFICE_CACHE to False in settings
POST_OFFICE_CACHE = False

## Optional: to use a non default cache backend, add a "post_office" entry in CACHES
CACHES = {
    'post_office': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

send_many()

send_many() is much more performant (generates less database queries) when sending a large number of emails. send_many() is almost identical to mail.send(), with the exception that it accepts a list of keyword arguments that you'd usually pass into mail.send():

from post_office import mail

first_email = {
    'sender': '[email protected]',
    'recipients': ['[email protected]'],
    'subject': 'Hi!',
    'message': 'Hi Alice!'
}
second_email = {
    'sender': '[email protected]',
    'recipients': ['[email protected]'],
    'subject': 'Hi!',
    'message': 'Hi Bob!'
}
kwargs_list = [first_email, second_email]

mail.send_many(kwargs_list)

Attachments are not supported with mail.send_many().

Running Tests

To run the test suite:

`which django-admin` test post_office --settings=post_office.test_settings --pythonpath=.

You can run the full test suite for all supported versions of Django and Python with:

tox

or:

python setup.py test

Integration with Celery

If your Django project runs in a Celery enabled configuration, you can use its worker to send out queued emails. Compared to the solution with cron (see above), or the solution with uWSGI timers (see below) this setup has the big advantage that queued emails are send immediately after they have been added to the mail queue. The delivery is still performed in a separate and asynchronous task, which prevents sending emails during the request/response-cycle.

If you configured Celery in your project and started the Celery worker, you should see something such as:

--------------- [email protected] v4.0 (latentcall)
--- ***** -----
-- ******* ---- [Configuration]
- *** --- * --- . broker:      amqp://guest@localhost:5672//
- ** ---------- . app:         __main__:0x1012d8590
- ** ---------- . concurrency: 8 (processes)
- ** ---------- . events:      OFF (enable -E to monitor this worker)
- ** ----------
- *** --- * --- [Queues]
-- ******* ---- . celery:      exchange:celery(direct) binding:celery
--- ***** -----

[tasks]
. post_office.tasks.cleanup_expired_mails
. post_office.tasks.send_queued_mail

Delivering emails through the Celery worker must be explicitly enabled:

# Put this in settings.py
POST_OFFICE = {
    ...
    'CELERY_ENABLED': True,
}

Emails will then be delivered immediately after they have been queued. In order to make this happen, the project's celery.py setup shall invoke the autodiscoverttasks function. In case of a temporary delivery failure, we might want retrying to send those emails by a periodic task. This can be scheduled with a simple Celery beat configuration, for instance through

app.conf.beat_schedule = {
    'send-queued-mail': {
        'task': 'post_office.tasks.send_queued_mail',
        'schedule': 600.0,
    },
}

The email queue now will be processed every 10 minutes. If you are using Django Celery Beat, then use the Django-Admin backend and add a periodic tasks for post_office.tasks.send_queued_mail.

Depending on your policy, you may also want to remove expired emails from the queue. This can be done by adding another periodic tasks for post_office.tasks.cleanup_mail, which may run once a week or month.

Integration with uWSGI

If setting up Celery is too daunting and you use uWSGI as application server, then uWSGI decorators can act as a poor men's scheduler. Just add this short snipped to the project's wsgi.py file:

from django.core.wsgi import get_wsgi_application

application = get_wsgi_application()

# add this block of code
try:
    import uwsgidecorators
    from django.core.management import call_command

    @uwsgidecorators.timer(10)
    def send_queued_mail(num):
        """Send queued mail every 10 seconds"""
        call_command('send_queued_mail', processes=1)

except ImportError:
    print("uwsgidecorators not found. Cron and timers are disabled")

Alternatively you can also use the decorator @uwsgidecorators.cron(minute, hour, day, month, weekday). This will schedule a task at specific times. Use -1 to signal any time, it corresponds to the * in cron.

Please note that uwsgidecorators are available only, if the application has been started with uWSGI. However, Django's internal ./manange.py runserver also access this file, therefore wrap the block into an exception handler as shown above.

This configuration can be useful in environments, such as Docker containers, where you don't have a running cron-daemon.

Signals

Each time an email is added to the mail queue, Post Office emits a special Django signal. Whenever a third party application wants to be informed about this event, it shall connect a callback function to the Post Office's signal handler email_queued, for instance:

from django.dispatch import receiver
from post_office.signals import email_queued

@receiver(email_queued)
def my_callback(sender, emails, **kwargs):
    print("Added {} mails to the sending queue".format(len(emails)))

The Emails objects added to the queue are passed as list to the callback handler.

Changelog

Full changelog can be found here.

Created and maintained by the cool guys at Stamps, Indonesia's most elegant CRM/loyalty platform.

django-post_office's People

Contributors

adamchainz avatar bashu avatar bergantine avatar christophmeissner avatar clickonchris avatar codertjay avatar cyberplant avatar domdinicola avatar edenilsonferreira00 avatar embedded1 avatar fendyh avatar fizista avatar franciscobmacedo avatar hockeybuggy avatar ivlevdenis avatar jessicarasidi avatar jhoos avatar jrief avatar kakulukia avatar marsha97 avatar mogost avatar petrprikryl avatar seiryuz avatar selwin avatar stefan-mihaila avatar timgates42 avatar wadevries avatar yprez avatar zagl avatar zwack avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-post_office's Issues

Priority must be an integer

Following the instructions in the readme:

>>> mail.send(
...     ['[email protected]'],
...     '[email protected]',
...     template='welcome_email',
...     context={'foo': 'bar'},
...     priority='now',
... )
Traceback (most recent call last):
  File "<input>", line 6, in <module>
  File "...../post_office/mail.py", line 68, in send
    for recipient in recipients]
  File "...../post_office/mail.py", line 45, in from_template
    headers=headers, priority=priority, status=status
  File "...../django/db/models/manager.py", line 149, in create
    return self.get_query_set().create(**kwargs)
  File "...../django/db/models/query.py", line 416, in create
    obj.save(force_insert=True, using=self.db)
  File "...../post_office/models.py", line 119, in save
    self.full_clean()
  File "...../django/db/models/base.py", line 926, in full_clean
    raise ValidationError(errors)
ValidationError: {'priority': [u"'now' value must be an integer."]}

The solution is to use PRIORITY.now instead of the string value.

Docs update needed? Or maybe send() should accept a string representation of "priority"?

Sending Queued mail failed, Django

[INFO]10-06-2014 12:00:02 PID 14886: Process started, sending 160 emails
Traceback (most recent call last):
File "/home/byld/placement/placement/manage.py", line 10, in
execute_from_command_line(sys.argv)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/init.py", line 399, in execute_from_command_line
utility.execute()
File "/usr/local/lib/python2.7/dist-packages/django/core/management/init.py", line 392, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 242, in run_from_argv
self.execute(_args, *_options.dict)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 285, in execute
output = self.handle(_args, *_options)
File "/usr/local/lib/python2.7/dist-packages/post_office/management/commands/send_queued_mail.py", line 23, in handle
with FileLock(tempfile.gettempdir() + "/post_office", timeout=1):
File "/usr/local/lib/python2.7/dist-packages/post_office/lockfile.py", line 111, in enter
if not self.is_locked():
File "/usr/local/lib/python2.7/dist-packages/post_office/lockfile.py", line 96, in is_locked
raise FileLocked()
post_office.lockfile.FileLocked
Traceback (most recent call last):
File "/home/byld/placement/placement/manage.py", line 10, in
execute_from_command_line(sys.argv)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/init.py", line 399, in execute_from_command_line
File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 242, in run_from_argv
self.execute(_args, *_options.dict)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 285, in execute
output = self.handle(_args, *_options)
File "/usr/local/lib/python2.7/dist-packages/post_office/management/commands/send_queued_mail.py", line 23, in handle
with FileLock(tempfile.gettempdir() + "/post_office", timeout=1):
File "/usr/local/lib/python2.7/dist-packages/post_office/lockfile.py", line 111, in enter
if not self.is_locked():
File "/usr/local/lib/python2.7/dist-packages/post_office/lockfile.py", line 96, in is_locked
raise FileLocked()
post_office.lockfile.FileLocked
Traceback (most recent call last):
File "/home/byld/placement/placement/manage.py", line 10, in
execute_from_command_line(sys.argv)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/init.py", line 399, in execute_from_command_line
utility.execute()
File "/usr/local/lib/python2.7/dist-packages/django/core/management/init.py", line 392, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 242, in run_from_argv
self.execute(_args, *_options.dict)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 285, in execute
output = self.handle(_args, *_options)
File "/usr/local/lib/python2.7/dist-packages/post_office/management/commands/send_queued_mail.py", line 23, in handle
with FileLock(tempfile.gettempdir() + "/post_office", timeout=1):
File "/usr/local/lib/python2.7/dist-packages/post_office/lockfile.py", line 111, in enter
if not self.is_locked():
File "/usr/local/lib/python2.7/dist-packages/post_office/lockfile.py", line 96, in is_locked
raise FileLocked()
post_office.lockfile.FileLocked
Traceback (most recent call last):
File "/home/byld/placement/placement/manage.py", line 10, in
execute_from_command_line(sys.argv)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 242, in run_from_argv
self.execute(_args, *_options.dict)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 285, in execute
output = self.handle(_args, *_options)
File "/usr/local/lib/python2.7/dist-packages/post_office/management/commands/send_queued_mail.py", line 23, in handle
with FileLock(tempfile.gettempdir() + "/post_office", timeout=1):
File "/usr/local/lib/python2.7/dist-packages/post_office/lockfile.py", line 111, in enter
if not self.is_locked():
File "/usr/local/lib/python2.7/dist-packages/post_office/lockfile.py", line 96, in is_locked
raise FileLocked()
post_office.lockfile.FileLocked
Traceback (most recent call last):
File "/home/byld/placement/placement/manage.py", line 10, in
execute_from_command_line(sys.argv)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/init.py", line 399, in execute_from_command_line
utility.execute()
File "/usr/local/lib/python2.7/dist-packages/django/core/management/init.py", line 392, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 242, in run_from_argv
self.execute(_args, *_options.dict)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 285, in execute
output = self.handle(_args, *_options)
File "/usr/local/lib/python2.7/dist-packages/post_office/management/commands/send_queued_mail.py", line 23, in handle
with FileLock(tempfile.gettempdir() + "/post_office", timeout=1):
File "/usr/local/lib/python2.7/dist-packages/post_office/lockfile.py", line 111, in enter
if not self.is_locked():
File "/usr/local/lib/python2.7/dist-packages/post_office/lockfile.py", line 96, in is_locked
raise FileLocked()
post_office.lockfile.FileLocked
[INFO]10-06-2014 12:03:40 PID 14886: Process finished, 160 emails attempted, 79 sent, 81 failed
[INFO]10-06-2014 12:03:40 PID 14886: 160 emails attempted, 79 sent, 81 failed

Recipients emails should support display name

Hi there,

I'm getting a ValidationError when sending mails with display names, e.g. John Smith [email protected]

Want to fix it?

Put the following lines in utils:

import re
from django.core.validators import EmailValidator

class FullEmailValidator(EmailValidator):
    """ Simple validator that passes for email addresses bearing a display name
    i.e. John Smith <[email protected]>
    """
    def __call__(self, value):
        try:
            res = super(FullEmailValidator, self).__call__(value)
        except ValidationError:
            try:
                split_address = re.match(r'(.+) \<(.+@.+)\>', value)
                display_name, email = split_address.groups()
                super(FullEmailValidator, self).__call__(email)
            except AttributeError:
                raise ValidationError

validate_email = FullEmailValidator()

Also, in utils replace in validate_comma_separated_emails() the call to validate_email by validate_email_with_name:

def validate_comma_separated_emails(value):
"""
Validate every email address in a comma separated list of emails.
"""
    if not isinstance(value, (tuple, list)):
        raise ValidationError('Email list must be a list/tuple.')

    for email in value:
        try:
            validate_email_with_name(email)
        except ValidationError:
            raise ValidationError('Invalid email: %s' % email, code='invalid')

cheers

Drop some indexes

Low cardinality indexes and infrequently used indexes:

  • Log.status
  • Email.priority

Unused indexes:

  • Log.date

Allow sending to more than one recipient

If I want to send an email to more then one recipient and just put to addresses into the recipients list I got two separate emails and not one with two recipients. How can I fix this?

Emails are being sent latest first and I need vice versa

I'm using the default priority which is Medium and a cron job to pick the emails out
of the queue for sending but emails are being sent lastest first due to "order_by(-priority)"

How can I achieve my goal which is sending out the oldest email in queue first?

thanks for this wonderful library

django admin popups for EmailTemplate ForeignKeys fails

 File ".env/local/lib/python2.7/site-packages/django/utils/html.py", line 65, in escapejs
   return mark_safe(force_text(value).translate(_js_escapes))
  TypeError: expected a character buffer object

this is easily fixed by replasing str with unicode in models.EmailTemplate.__unicode__

Is there an option to send plain text emails?

Hi, thanks for the great project!

Currently to send plain text emails I just don't set the html_content field in the email template.

Is there a way to do this conditionally? Such as a setting to send plain text even if the email template has html_content set?

Thanks

Migrations failing on upgrade from Python 2.7 to Python 3.4/Django 1.5 to Django 1.7

I'm upgrading a web app I built for my company from Python 2.7 to Python 3.4 and Django 1.5 to Django 1.7. I followed the 1.7 release note instructions for porting from South to native Django migrations and most things seemed to work fine. My test suite returned some errors on things using emails. Visiting the post_office Email change page in the Django admin resulted in this error message with post_office 1.0.0. I tried using the latest post_office from Git and it also failed:

Django Version: 1.7
Python Version: 3.4.1
Installed Applications:
('django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.admin',
 'braces',
 'ckeditor',
 'crispy_forms',
 'django_bleach',
 'django_comments',
 'haystack',
 'post_office',
 'register',
 'registration',
 'taggit',
 'community',
 'complaints',
 'decisions',
 'events',
 'guidance',
 'kpis',
 'news',
 'programme',
 'quanda',
 'SARs',
 'schemes',
 'travel',
 'debug_toolbar')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'debug_toolbar.middleware.DebugToolbarMiddleware')


Traceback:
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
  111.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/contrib/admin/options.py" in wrapper
  567.                 return self.admin_site.admin_view(view)(*args, **kwargs)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/utils/decorators.py" in _wrapped_view
  105.                     response = view_func(request, *args, **kwargs)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/views/decorators/cache.py" in _wrapped_view_func
  52.         response = view_func(request, *args, **kwargs)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/contrib/admin/sites.py" in inner
  204.             return view(request, *args, **kwargs)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/utils/decorators.py" in _wrapper
  29.             return bound_func(*args, **kwargs)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/utils/decorators.py" in _wrapped_view
  105.                     response = view_func(request, *args, **kwargs)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/utils/decorators.py" in bound_func
  25.                 return func.__get__(self, type(self))(*args2, **kwargs2)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/contrib/admin/options.py" in changelist_view
  1576.             selection_note=_('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/models/query.py" in __len__
  122.         self._fetch_all()
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/models/query.py" in _fetch_all
  966.             self._result_cache = list(self.iterator())
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/models/query.py" in iterator
  265.         for row in compiler.results_iter():
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/models/sql/compiler.py" in results_iter
  700.         for rows in self.execute_sql(MULTI):
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/models/sql/compiler.py" in execute_sql
  786.             cursor.execute(sql, params)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/debug_toolbar/panels/sql/tracking.py" in execute
  174.         return self._record(self.cursor.execute, sql, params)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/debug_toolbar/panels/sql/tracking.py" in _record
  104.             return method(sql, params)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
  81.             return super(CursorDebugWrapper, self).execute(sql, params)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
  65.                 return self.cursor.execute(sql, params)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/utils.py" in __exit__
  94.                 six.reraise(dj_exc_type, dj_exc_value, traceback)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/utils/six.py" in reraise
  549.             raise value.with_traceback(tb)
File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
  65.                 return self.cursor.execute(sql, params)

Exception Type: ProgrammingError at /sN0won2june/post_office/email/
Exception Value: column post_office_email.cc does not exist
LINE 1: ...ce_email"."from_email", "post_office_email"."to", "post_offi...

I think the problem might lie in the migrations. These seemed to work with 1.0.0 but, using the Git master, I got this error:

Applying post_office.0001_initial...Traceback (most recent call last):
  File "manage.py", line 11, in <module>
    execute_from_command_line(sys.argv)
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/core/management/commands/migrate.py", line 160, in handle
    executor.migrate(targets, plan, fake=options.get("fake", False))
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/migrations/executor.py", line 63, in migrate
    self.apply_migration(migration, fake=fake)
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/migrations/executor.py", line 91, in apply_migration
    if self.detect_soft_applied(migration):
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/migrations/executor.py", line 135, in detect_soft_applied
    apps = project_state.render()
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/migrations/state.py", line 67, in render
    model.render(self.apps)
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/migrations/state.py", line 311, in render
    body,
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/models/base.py", line 171, in __new__
    new_class.add_to_class(obj_name, obj)
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/models/base.py", line 300, in add_to_class
    value.contribute_to_class(cls, name)
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/models/fields/related.py", line 1572, in contribute_to_class
    super(ForeignObject, self).contribute_to_class(cls, name, virtual_only=virtual_only)
  File "/home/gary/intranet_upgrade/lib/python3.4/site-packages/django/db/models/fields/related.py", line 264, in contribute_to_class
    'app_label': cls._meta.app_label.lower()
TypeError: unsupported operand type(s) for %: 'bytes' and 'dict'

Normally I'd post something like this on Stack Overflow but this one looked like it might be a bug so I'm posting here. Let me know if I'm wrong and I'll move to SO. Sorry not to suggest a fix but this one looks beyond me.

Allow content to be rendered during delivery

For emails that are rendered from templates, we can avoid storing the rendered content/HTML content by rendering them from the template just before delivery. Depending on usage, this may result in significant space savings.

Suggestion: send mode with "fire and forget"

Hi there,

In some cases (e.g. sending an URL for resetting a password), you may not want a successfully-sent mail to remain in the database.
I would suggest the addition of a "fire and forget" send mode that would delete the email if it is sent successfully.

regards
LAI

Post office spams my database with error logs

I installed post-office and it works fine except that it put tons of error messages about broken links in its
email database. It is fine for me to fix the errors, but I feel my database is not the right place for that kind of error log, but I do not know how to stop that. How shoud I set my settings? What might be causing this?

postgresql-9.1-main.log => UTC LOG: unexpected EOF on client connection

This message is filling up my postgres log when I'm using the management commands. The problems occur because connections are not closed properly. I fixed it this way:

===== cleanup_mail.py & send_queued_mail.py =====
==> from django import db
from django.core.management.base import BaseCommand

===== cleanup_mail.py =====
Email.objects.only('id').filter(created__lt=cutoff_date).delete()
==> db.close_connection()

===== send_queued_mail.py =====
send_queued(options['processes'], options['log_level'])
==> db.close_connection()

admin shows inconsistent behavior

Hi,

the admin integration of post_office seems inconsistent and does not work (at least, not reliably).

  • Usually, post_office does not appear at all in the admin interface
  • Sometimes, post_office shows up, but then not all tables of my own app appear

I've not been able to reliably ascertain when which case happens; it looks a bit as if the order in INSTALLED_APPs matters, but not reliable either. Database newly created each time.

  • Django: 1.6.4
  • Post office django-post-office (0.8.4)
  • json_field: jsonfield (0.9.20)
  • python 2.7.2

Any ideas where to start looking?

thanks a lot,

Holger

Signature of post_office.utils.send_mail does not match django.core.mail.send_mail

Post Office's send_mail function unfortunately does not accept the same arguments as Django's own counterpart. This breaks things when trying to use Post Office to send mails when an app tries to use arguments which Django does accept, such as fail_silently. This is particularly an issue when using the Postman package, which has a setting on which send_mail function to use and could thus be configured to use Post Office's one (see https://bitbucket.org/psam/django-postman/src/b307189ff3f7e68b6c485175936eb790a59830b7/postman/utils.py?at=default#cl-23 for this feature). Because Postman innocently tries to use fail_silently when sending a mail, Python will whine about the arguments.

An easy but functioning workaround which we use is to use a very simple wrapper function which has a **kwargs in the signature to catch any arguments Post Office does not care about. For example:

def send_mail(subject, message, from_email, recipient_list, html_message='',
              scheduled_time=None, headers=None, priority=mail.PRIORITY.medium,
              **kwargs):
    # A wrapper for post_office.send_mail, because that doesn't have a **kwargs
    # in the signature, and Postman fails on it because of that.
    return post_office_send_mail(
        subject, message, from_email, recipient_list,
        html_message=html_message, scheduled_time=scheduled_time,
        headers=headers, priority=priority)

But I think this is a bug in Post Office. A tiny one, but still. Using **kwargs as well in Post Office is a simple but workable fix, but I don't know if that is proper in the context of Post Office.

502 bad gateway

Sometimes I get 502 error.

What I see in logs:
File "/usr/lib/python2.6/logging/init.py", line 679, in handle
self.emit(record)
File "/django/utils/log.py", line 113, in emit
mail.mail_admins(subject, message, fail_silently=True, html_message=html_message)
File "/django/core/mail/init.py", line 98, in mail_admins
mail.send(fail_silently=fail_silently)
File "/django/core/mail/message.py", line 255, in send
return self.get_connection(fail_silently).send_messages([self])
File "/post_office/backends.py", line 43, in send_messages
headers=headers, priority=PRIORITY.medium)
File "/django/db/models/manager.py", line 149, in create
return self.get_query_set().create(**kwargs)
File "/django/db/models/query.py", line 416, in create
obj.save(force_insert=True, using=self.db)
File "/post_office/models.py", line 119, in save
self.full_clean()
File "/django/db/models/base.py", line 926, in full_clean
raise ValidationError(errors)
django.core.exceptions.ValidationError: {'from_email': [u'Enter a valid e-mail address.']}

I have regular settings.DEFAULT_FROM_EMAIL
and EMAIL_BACKEND = 'post_office.EmailBackend'

django 1.5

Celery integration

I wonder if you have some recommendation to integrate it with Celery ?

Filesystem-based Templates?

Hi, thanks for the great work!

What's the reason behind database-backed email templates? Is there a way to store templates from the filesystem instead? For example, I want email templates under revision control.

Daniel

Choose Content Disposition

Is it possible to add an image as inline attachment.

How it works now when using create_attachements:
Content-Disposition: attachment; filename="logo.png"

How I'd like to attach image:
Content-ID: <BV1AYY> Content-Disposition: inline; filename="logo.png"

Test fails due to invalid email DEFAULT_FROM_EMAIL address

ERROR: test_default_sender (post_office.tests.models.ModelTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./post_office/tests/models.py", line 137, in test_default_sender
    emails = send(['[email protected]'], subject='foo')
  File "./post_office/mail.py", line 60, in send
    priority=priority)
  File "./post_office/utils.py", line 30, in send_mail
    priority=priority
  File "/Users/wouter/dev/envs/postoffice/lib/python2.6/site-packages/django/db/models/manager.py", line 149, in create
    return self.get_query_set().create(**kwargs)
  File "/Users/wouter/dev/envs/postoffice/lib/python2.6/site-packages/django/db/models/query.py", line 402, in create
    obj.save(force_insert=True, using=self.db)
  File "./post_office/models.py", line 108, in save
    self.full_clean()
  File "/Users/wouter/dev/envs/postoffice/lib/python2.6/site-packages/django/db/models/base.py", line 926, in full_clean
    raise ValidationError(errors)
ValidationError: {'from_email': [u'Enter a valid e-mail address.']}

----------------------------------------------------------------------

This is because it uses the DEFAULT_FROM_EMAIL from global_settings (Django 1.5.1), which is 'webmaster@localhost', which does not pass the validators.validate_email_with_name's regex, which requires the address to end with a tld.

Simple fix; set DEFAULT_FROM_EMAIL in test_settings to '[email protected]'.
Or; relax the validator's regex.

Allow default priority to be customized

Currently, all mail sending will have default priority of PRIORITY.medium,

Would it be acceptable if we introduce new setting, like POST_OFFICE_DEFAULT_PRIORITY?

In some use case(my small project for example), we need to send email mostly with PRIORITY.now, only in some cases that we use PRIORITY.medium. Calling mail.send has become cumbersome due to writing PRIORITY.now everywhere.

More direct replacement: add configuration option to always send email immediately

I don't have a large volume of email, and am more using django-post_office for the templating functionality.

If there were a settings configuration option for email to just send immediately (including from all other third-party apps, so setting priority isn't an option), rather than being queued, then it would be a direct replacement for the original send_mail and not require setting up cron or the similar just to send email.

(I'll have a look at doing this myself when I get a moment, so this is more just noting the feature request, not expecting an implementation.)

Running send_queued_mail every 60s on Heroku - opens a new db connection every time - maxing out my connections

Does send_queued_mail close it's db connection when done? Doesn't seem to be. Perhaps it is how I am calling it via a scheduler so it runs every 60s. That file below as an fyi


import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'anonqaproject.settings'
import logging
logging.basicConfig()
from apscheduler.scheduler import Scheduler
from post_office import utils
sched = Scheduler()

@sched.interval_schedule(minutes=1)
def timed_job():
utils.send_queued_mail()

sched.start()
while True:

pass

Context fields visible in django admin edit page.

We can user template context variable in mail content, thats very good.
Can we some how display the context variables available for a given template in the edit page. This will help editor to use them without looking at the code that sends the mail.

I had a similar implementation where i added a field (context_variables) which displays the template variables available.
Problem with my application was that, editor may delete the values from there.
Some how we should set the permissions that if the editor is super_user, he can edit the field context_variables, for others this will be non-editable field.

Simplify integration with task queues by adding default priority setting

I want to use Celery to dispatch my emails asynchronously and find setting priority=PRIORITY.now on each send() a bit too verbose and error prone (emails won't get sent if forgotten!).

But if I understand things correctly, that's what should be done when using a backend that uses workers to send the emails.

Hence, I suggest (and am willing to send a PR) to introduce another setting which allows specifying the default priority. Just wanted to check if I might miss something and if it's agreed on before I get to work on the PR.

Having issues automating mail send through celery - KeyError: 'lockfile'

Hi there,

I'm trying to periodically send out queued up mail with celery. The way I'm going about this is by creating a task that I will periodically call, which basically just calls into the function that triggers mail send. This function looks like this:

from post_office import mail
from post_office.management.commands import send_queued_mail

@task
def send_emails():
    cmd = send_queued_mail.Command()
    cmd.execute(processes=1)

When this task gets called, I see the following stack trace within RabbitMQ:

[2014-10-12 22:15:43,877: ERROR/MainProcess] Task booker.tasks.send_emails[26d93b2e-20ed-448f-ae19-29615a2a7906] raised unexpected: KeyError('lockfile',)
Traceback (most recent call last):
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/celery/app/trace.py", line 240, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/celery/app/trace.py", line 437, in __protected_call__
    return self.run(*args, **kwargs)
  File "/Users/chris/dev/bitbucket/myapp/myapp/booker/tasks.py", line 11, in send_emails
    cmd.execute(processes=num_processes)
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/django/core/management/base.py", line 285, in execute
    output = self.handle(*args, **options)
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/post_office/management/commands/send_queued_mail.py", line 29, in handle
    options['lockfile'])
KeyError: 'lockfile'
[2014-10-12 22:15:43,887: ERROR/MainProcess] Task booker.tasks.send_emails[b61c6620-cd4c-472b-9ebb-e8cf7ef782e7] raised unexpected: KeyError('lockfile',)
Traceback (most recent call last):
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/celery/app/trace.py", line 240, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/celery/app/trace.py", line 437, in __protected_call__
    return self.run(*args, **kwargs)
  File "/Users/chris/dev/bitbucket/myapp/myapp/booker/tasks.py", line 11, in send_emails
    cmd.execute(processes=num_processes)
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/django/core/management/base.py", line 285, in execute
    output = self.handle(*args, **options)
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/post_office/management/commands/send_queued_mail.py", line 29, in handle
    options['lockfile'])
KeyError: 'lockfile'
[2014-10-12 22:15:43,888: ERROR/MainProcess] Task booker.tasks.send_emails[63c97beb-d56c-44ec-ac0a-07d1b0b77a69] raised unexpected: KeyError('lockfile',)
Traceback (most recent call last):
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/celery/app/trace.py", line 240, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/celery/app/trace.py", line 437, in __protected_call__
    return self.run(*args, **kwargs)
  File "/Users/chris/dev/bitbucket/myapp/myapp/booker/tasks.py", line 11, in send_emails
    cmd.execute(processes=num_processes)
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/django/core/management/base.py", line 285, in execute
    output = self.handle(*args, **options)
  File "/Users/chris/Envs/myappvenv/lib/python2.7/site-packages/post_office/management/commands/send_queued_mail.py", line 29, in handle
    options['lockfile'])
KeyError: 'lockfile'

I'm not seeing any clear documentation on the expected usage of this with Celery, so I may be misusing it entirely... Is this a misuse of the library entirely, or is there a deeper issue at play? How do I trigger periodically emptying the queue and sending out pending emails?

Migration 0011 error

Hitting this migration error today, anyone else experience this?

Running migrations for post_office:
 - Migrating forwards to 0011_auto__chg_field_email_context.
 > post_office:0011_auto__chg_field_email_context
FATAL ERROR - The following SQL query failed: ALTER TABLE "post_office_email" ALTER COLUMN "context" TYPE json, ALTER COLUMN "context" DROP NOT NULL, ALTER COLUMN "context" DROP DEFAULT;
The error was: column "context" cannot be cast automatically to type json
HINT:  Specify a USING expression to perform the conversion.

Error django 1.7 - RemovedInDjango18Warning

Hi
I got this error when i running my project with django 1.7

.virtualenvs/ownproject/lib/python2.7/site-packages/django/forms/widgets.py:143: RemovedInDjango18Warning: EmailAdmin.queryset method should be renamed get_queryset.
.new(mcs, name, bases, attrs))

any clue how to fix?

Python3 error with django-jsonfield

Steps to reproduce:

  1. install django-post_office into a python3.3 virtualenv
  2. create a template and send 1 email
  3. Try to view that email at /admin/post_office/email/1/

Results: NameError: global name 'basestring' is not defined

This occurs in django-jsonfield (widgets.py, line 12).

So it seems that django-jsonfield is not Python3 compatible. Interestingly there is a separate project 'jsonfield' which is Python3 compatible and django-post_office seems to work well with it.

FileLockException: Timeout occured.

Hi,

Every time I try to send mails I get "FileLockException: Timeout occured.". It worked great for months and now It doesn't. Reboot doesn't help. Any ideas?

Bests,
Philip

Attachment support?

Hi,
Is there support for sending email attachments in post_office?
I couldn't find any mention in the code or the docs...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.