import email
import email.mime.multipart
+import email.mime.text
import email.utils
import logging
import subprocess
-import textwrap
-import tornado.gen
+import tornado.locale
import tornado.template
+from . import accounts
from . import misc
from .decorators import *
return tornado.template.Loader(templates_dir, autoescape=None)
+ def make_recipient(self, recipient):
+ # Use the contact instead of the account
+ if isinstance(recipient, accounts.Account):
+ recipient = "%s <%s>" % (recipient, recipient.email)
+
+ # Fall back to pass on strings
+ return recipient
+
def make_msgid(self):
return email.utils.make_msgid("ipfire", domain="ipfire.org")
def bounce_email_address(self):
return self.settings.get("bounce_email_address")
- def send(self, recipients, message, priority=None, headers={}):
+ def _send(self, recipients, message, sender=None, priority=0):
+ if not recipients:
+ raise ValueError("Empty list of recipients")
+
+ # Format recipients
+ recipients = [self.make_recipient(r) for r in recipients]
+
+ res = self.db.get("INSERT INTO messages(message, priority, envelope_recipients) \
+ VALUES(%s, %s, %s) RETURNING id", message, priority, recipients)
+
+ logging.debug("Message queued with ID %s" % res.id)
+
+ def send(self, recipients, message, priority=0, headers={}):
# Convert message from string
if not isinstance(message, email.message.Message):
message = email.message_from_string(message)
except KeyError:
message.add_header(k, v)
+ # Read recipients from To: header
+ if not recipients:
+ recipients = message.get("To").split(", ")
+
# Add date if the message doesn't have one already
if "Date" not in message:
message.add_header("Date", email.utils.formatdate())
# Send the message
self._send(recipients, message.as_string(), priority=priority)
- def send_template(self, template_name, recipients,
- sender=None, priority=None, headers={}, **kwargs):
+ def send_template(self, template_name, recipients=[],
+ sender=None, priority=0, headers={}, **kwargs):
"""
Send a message based on the given template
"""
+ locale = tornado.locale.get("en_US")
+
# Create the required namespace to render the message
namespace = {
# Generic Stuff
"backend" : self.backend,
+
+ # Locale
+ "locale" : locale,
+ "_" : locale.translate,
}
namespace.update(kwargs)
- # Create a MIMEMultipart message.
- message = email.mime.multipart.MIMEMultipart()
+ # Create an alternating multipart message to show HTML or text
+ message = email.mime.multipart.MIMEMultipart("alternative")
- for extension, mime_type in (("txt", "plain"), ("html", "html")):
+ for extension, mimetype in (("txt", "plain"), ("html", "html")):
try:
t = self.template_loader.load("%s.%s" % (template_name, extension))
- except IOError:
- continue
+ except IOError as e:
+ # Ignore if the HTML template does not exist
+ if extension == "html":
+ continue
+
+ # Raise all other exceptions
+ raise e
# Render the message
try:
# Parse the message and extract the header
message_part = email.message_from_string(message_part.decode())
- for k, v in list(message_part.items()):
+
+ for header in message_part:
try:
- message.replace_header(k, v)
+ message.replace_header(header, message_part[header])
except KeyError:
- message.add_header(k, v)
-
- message_body = message_part.get_payload()
-
- # Wrap texts to 120 characters per line
- if mime_type == "plain":
- message_body = wrap(message_body, 120)
+ message.add_header(header, message_part[header])
# Create a MIMEText object out of it
- message_part = email.mime.text.MIMEText(message_body, mime_type, "utf-8")
+ message_part = email.mime.text.MIMEText(
+ message_part.get_payload(), mimetype)
# Attach the parts to the mime container.
# According to RFC2046, the last part of a multipart message
# is preferred.
- alternative.attach(message_part)
-
- # Add alternative section to outer message
- message.attach(alternative)
+ message.attach(message_part)
# Send the message
self.send(recipients, message, priority=priority, headers=headers)
if self.backend.debug:
self.template_loader.reset()
+ async def send_cli(self, template, recipient):
+ """
+ Send a test message from the CLI
+ """
+ account = self.backend.accounts.get_by_mail(recipient)
+
+ return self.send_template(template, recipients=[recipient,],
+ account=account)
+
class Queue(misc.Object):
@property
WHERE time_sent IS NULL \
ORDER BY priority DESC, time_created ASC")
- @tornado.gen.coroutine
- def send_all(self):
+ async def send_all(self):
# Sends all messages
for message in self.messages:
self._sendmail(message)
# Parse the message from what is in the database
msg = email.message_from_string(message.message)
- logging.info("Sending a message %s to: %s" % (
+ logging.debug("Sending a message %s to: %s" % (
msg.get("Subject"), ", ".join(message.envelope_recipients)
))
"-oi",
# Envelope Sender
- "-f", msg.get("From"),
+ "-f", msg.get("From") or "no-reply@ipfire.org",
]
# Envelope Recipients
self.db.execute("UPDATE messages SET time_sent = NOW() \
WHERE id = %s", message.id)
- @tornado.gen.coroutine
def cleanup(self):
logging.debug("Cleaning up message queue")
self.db.execute("DELETE FROM messages \
- WHERE time_sent IS NOT NULL AND time_sent >= NOW() + '1 day'::interval")
-
-
-def wrap(text, width):
- s = []
-
- for paragraph in text.split("\n\n"):
- paragraph = textwrap.wrap(paragraph, width,
- break_long_words=False, replace_whitespace=False)
-
- if paragraph:
- s.append("\n".join(paragraph))
-
- return "\n\n".join(s)
+ WHERE time_sent IS NOT NULL AND time_sent <= NOW() - '30 day'::interval")