mirror of
https://github.com/simple-login/app.git
synced 2025-09-12 09:34:28 +08:00
Allow to define more than one smtp server
This commit is contained in:
parent
6f391511b0
commit
4e9b2f5995
4 changed files with 85 additions and 71 deletions
|
@ -144,8 +144,8 @@ MAX_NB_SUBDOMAIN = 5
|
||||||
ENFORCE_SPF = "ENFORCE_SPF" in os.environ
|
ENFORCE_SPF = "ENFORCE_SPF" in os.environ
|
||||||
|
|
||||||
# override postfix server locally
|
# override postfix server locally
|
||||||
# use 240.0.0.1 here instead of 10.0.0.1 as existing SL instances use the 240.0.0.0 network
|
POSTFIX_SERVERS = get_env_csv("POSTFIX_SERVER", "240.0.0.1")
|
||||||
POSTFIX_SERVER = os.environ.get("POSTFIX_SERVER", "240.0.0.1")
|
POSTFIX_BACKUP_SERVERS = get_env_csv("POSTFIX_BACKUP_SERVER", "")
|
||||||
|
|
||||||
DISABLE_REGISTRATION = "DISABLE_REGISTRATION" in os.environ
|
DISABLE_REGISTRATION = "DISABLE_REGISTRATION" in os.environ
|
||||||
|
|
||||||
|
|
|
@ -1346,11 +1346,12 @@ def spf_pass(
|
||||||
@cached(cache=TTLCache(maxsize=2, ttl=20))
|
@cached(cache=TTLCache(maxsize=2, ttl=20))
|
||||||
def get_smtp_server():
|
def get_smtp_server():
|
||||||
LOG.d("get a smtp server")
|
LOG.d("get a smtp server")
|
||||||
|
server = random.choice(config.POSTFIX_SERVERS)
|
||||||
if config.POSTFIX_SUBMISSION_TLS:
|
if config.POSTFIX_SUBMISSION_TLS:
|
||||||
smtp = SMTP(config.POSTFIX_SERVER, 587)
|
smtp = SMTP(server, 587)
|
||||||
smtp.starttls()
|
smtp.starttls()
|
||||||
else:
|
else:
|
||||||
smtp = SMTP(config.POSTFIX_SERVER, config.POSTFIX_PORT)
|
smtp = SMTP(server, config.POSTFIX_PORT)
|
||||||
|
|
||||||
return smtp
|
return smtp
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import base64
|
||||||
import email
|
import email
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
@ -144,10 +145,51 @@ class MailSender:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _send_to_smtp(self, send_request: SendRequest, retries: int) -> bool:
|
def _send_to_smtp(self, send_request: SendRequest, retries: int) -> bool:
|
||||||
|
servers_to_try = config.POSTFIX_SERVERS.copy()
|
||||||
|
random.shuffle(servers_to_try)
|
||||||
|
if config.POSTFIX_BACKUP_SERVERS:
|
||||||
|
servers_to_try.extend(config.POSTFIX_BACKUP_SERVERS)
|
||||||
|
servers_tried = 0
|
||||||
|
for server_hostname in servers_to_try:
|
||||||
|
servers_tried += 1
|
||||||
start = time.time()
|
start = time.time()
|
||||||
try:
|
try:
|
||||||
|
return self.__send_to_server(server_hostname, send_request)
|
||||||
|
except (
|
||||||
|
SMTPException,
|
||||||
|
ConnectionRefusedError,
|
||||||
|
TimeoutError,
|
||||||
|
) as e:
|
||||||
|
LOG.w(f"Got error {e} while sending email to {server_hostname}")
|
||||||
|
newrelic.agent.record_custom_event("SmtpError", {"error": e.__class__})
|
||||||
|
finally:
|
||||||
|
newrelic.agent.record_custom_metric(
|
||||||
|
"Custom/smtp_servers_tried", servers_tried
|
||||||
|
)
|
||||||
|
newrelic.agent.record_custom_metric(
|
||||||
|
"Custom/smtp_sending_time", time.time() - start
|
||||||
|
)
|
||||||
|
if retries > 0:
|
||||||
|
LOG.warning(
|
||||||
|
f"Retrying sending email due to error. {retries} retries left. Will wait {0.3*retries} seconds."
|
||||||
|
)
|
||||||
|
time.sleep(0.3 * retries)
|
||||||
|
return self._send_to_smtp(send_request, retries - 1)
|
||||||
|
else:
|
||||||
|
if send_request.ignore_smtp_errors:
|
||||||
|
LOG.e("Ignore smtp error")
|
||||||
|
return False
|
||||||
|
LOG.e(
|
||||||
|
f"Could not send message to smtp server {config.POSTFIX_SERVERS}:{config.POSTFIX_PORT}"
|
||||||
|
)
|
||||||
|
if config.SAVE_UNSENT_DIR:
|
||||||
|
send_request.save_request_to_unsent_dir()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __send_to_server(self, server_host: str, send_request: SendRequest):
|
||||||
|
start = time.time()
|
||||||
with SMTP(
|
with SMTP(
|
||||||
config.POSTFIX_SERVER,
|
server_host,
|
||||||
config.POSTFIX_PORT,
|
config.POSTFIX_PORT,
|
||||||
timeout=config.POSTFIX_TIMEOUT,
|
timeout=config.POSTFIX_TIMEOUT,
|
||||||
) as smtp:
|
) as smtp:
|
||||||
|
@ -155,10 +197,10 @@ class MailSender:
|
||||||
smtp.starttls()
|
smtp.starttls()
|
||||||
|
|
||||||
elapsed = time.time() - start
|
elapsed = time.time() - start
|
||||||
LOG.d("getting a smtp connection takes seconds %s", elapsed)
|
LOG.d(
|
||||||
newrelic.agent.record_custom_metric(
|
f"Getting a smtp connection to {server_host} takes seconds {elapsed:.3} seconds"
|
||||||
"Custom/smtp_connection_time", elapsed
|
|
||||||
)
|
)
|
||||||
|
newrelic.agent.record_custom_metric("Custom/smtp_connection_time", elapsed)
|
||||||
|
|
||||||
# smtp.send_message has UnicodeEncodeError
|
# smtp.send_message has UnicodeEncodeError
|
||||||
# encode message raw directly instead
|
# encode message raw directly instead
|
||||||
|
@ -177,36 +219,7 @@ class MailSender:
|
||||||
send_request.mail_options,
|
send_request.mail_options,
|
||||||
send_request.rcpt_options,
|
send_request.rcpt_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
newrelic.agent.record_custom_metric(
|
|
||||||
"Custom/smtp_sending_time", time.time() - start
|
|
||||||
)
|
|
||||||
return True
|
return True
|
||||||
except (
|
|
||||||
SMTPException,
|
|
||||||
ConnectionRefusedError,
|
|
||||||
TimeoutError,
|
|
||||||
) as e:
|
|
||||||
newrelic.agent.record_custom_metric(
|
|
||||||
"Custom/smtp_sending_time", time.time() - start
|
|
||||||
)
|
|
||||||
newrelic.agent.record_custom_event("SmtpError", {"error": e.__class__})
|
|
||||||
if retries > 0:
|
|
||||||
LOG.warning(
|
|
||||||
f"Retrying sending email due to error {e}. {retries} retries left. Will wait {0.3*retries} seconds."
|
|
||||||
)
|
|
||||||
time.sleep(0.3 * retries)
|
|
||||||
return self._send_to_smtp(send_request, retries - 1)
|
|
||||||
else:
|
|
||||||
if send_request.ignore_smtp_errors:
|
|
||||||
LOG.e(f"Ignore smtp error {e}")
|
|
||||||
return False
|
|
||||||
LOG.e(
|
|
||||||
f"Could not send message to smtp server {config.POSTFIX_SERVER}:{config.POSTFIX_PORT}"
|
|
||||||
)
|
|
||||||
if config.SAVE_UNSENT_DIR:
|
|
||||||
send_request.save_request_to_unsent_dir()
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
mail_sender = MailSender()
|
mail_sender = MailSender()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
|
import socket
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import socket
|
|
||||||
from email.message import Message
|
from email.message import Message
|
||||||
from random import random
|
from random import random
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
@ -9,13 +9,13 @@ from typing import Callable
|
||||||
import pytest
|
import pytest
|
||||||
from aiosmtpd.controller import Controller
|
from aiosmtpd.controller import Controller
|
||||||
|
|
||||||
|
from app import config
|
||||||
from app.email import headers
|
from app.email import headers
|
||||||
from app.mail_sender import (
|
from app.mail_sender import (
|
||||||
mail_sender,
|
mail_sender,
|
||||||
SendRequest,
|
SendRequest,
|
||||||
load_unsent_mails_from_fs_and_resend,
|
load_unsent_mails_from_fs_and_resend,
|
||||||
)
|
)
|
||||||
from app import config
|
|
||||||
|
|
||||||
|
|
||||||
def create_dummy_send_request() -> SendRequest:
|
def create_dummy_send_request() -> SendRequest:
|
||||||
|
@ -105,8 +105,8 @@ def compare_send_requests(expected: SendRequest, request: SendRequest):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_mail_sender_save_unsent_to_disk(server_fn):
|
def test_mail_sender_save_unsent_to_disk(server_fn):
|
||||||
original_postfix_server = config.POSTFIX_SERVER
|
original_postfix_server = config.POSTFIX_SERVERS
|
||||||
config.POSTFIX_SERVER = "localhost"
|
config.POSTFIX_SERVERS = ["localhost"]
|
||||||
config.NOT_SEND_EMAIL = False
|
config.NOT_SEND_EMAIL = False
|
||||||
config.POSTFIX_SUBMISSION_TLS = False
|
config.POSTFIX_SUBMISSION_TLS = False
|
||||||
config.POSTFIX_PORT = server_fn()
|
config.POSTFIX_PORT = server_fn()
|
||||||
|
@ -122,14 +122,14 @@ def test_mail_sender_save_unsent_to_disk(server_fn):
|
||||||
)
|
)
|
||||||
compare_send_requests(loaded_send_request, send_request)
|
compare_send_requests(loaded_send_request, send_request)
|
||||||
finally:
|
finally:
|
||||||
config.POSTFIX_SERVER = original_postfix_server
|
config.POSTFIX_SERVERS = original_postfix_server
|
||||||
config.NOT_SEND_EMAIL = True
|
config.NOT_SEND_EMAIL = True
|
||||||
|
|
||||||
|
|
||||||
@mail_sender.store_emails_test_decorator
|
@mail_sender.store_emails_test_decorator
|
||||||
def test_send_unsent_email_from_fs():
|
def test_send_unsent_email_from_fs():
|
||||||
original_postfix_server = config.POSTFIX_SERVER
|
original_postfix_server = config.POSTFIX_SERVERS
|
||||||
config.POSTFIX_SERVER = "localhost"
|
config.POSTFIX_SERVERS = ["localhost"]
|
||||||
config.NOT_SEND_EMAIL = False
|
config.NOT_SEND_EMAIL = False
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
try:
|
try:
|
||||||
|
@ -137,7 +137,7 @@ def test_send_unsent_email_from_fs():
|
||||||
send_request = create_dummy_send_request()
|
send_request = create_dummy_send_request()
|
||||||
assert not mail_sender.send(send_request, 1)
|
assert not mail_sender.send(send_request, 1)
|
||||||
finally:
|
finally:
|
||||||
config.POSTFIX_SERVER = original_postfix_server
|
config.POSTFIX_SERVERS = original_postfix_server
|
||||||
config.NOT_SEND_EMAIL = True
|
config.NOT_SEND_EMAIL = True
|
||||||
saved_files = os.listdir(config.SAVE_UNSENT_DIR)
|
saved_files = os.listdir(config.SAVE_UNSENT_DIR)
|
||||||
assert len(saved_files) == 1
|
assert len(saved_files) == 1
|
||||||
|
@ -154,8 +154,8 @@ def test_send_unsent_email_from_fs():
|
||||||
|
|
||||||
@mail_sender.store_emails_test_decorator
|
@mail_sender.store_emails_test_decorator
|
||||||
def test_failed_resend_does_not_delete_file():
|
def test_failed_resend_does_not_delete_file():
|
||||||
original_postfix_server = config.POSTFIX_SERVER
|
original_postfix_server = config.POSTFIX_SERVERS
|
||||||
config.POSTFIX_SERVER = "localhost"
|
config.POSTFIX_SERVERS = ["localhost"]
|
||||||
config.NOT_SEND_EMAIL = False
|
config.NOT_SEND_EMAIL = False
|
||||||
try:
|
try:
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
@ -176,7 +176,7 @@ def test_failed_resend_does_not_delete_file():
|
||||||
# No more emails are stored in disk
|
# No more emails are stored in disk
|
||||||
assert saved_files == os.listdir(config.SAVE_UNSENT_DIR)
|
assert saved_files == os.listdir(config.SAVE_UNSENT_DIR)
|
||||||
finally:
|
finally:
|
||||||
config.POSTFIX_SERVER = original_postfix_server
|
config.POSTFIX_SERVERS = original_postfix_server
|
||||||
config.NOT_SEND_EMAIL = True
|
config.NOT_SEND_EMAIL = True
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue