feat: implement email service backend with Hostinger Mail API integration
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 46s

This commit is contained in:
vickytechkey 2026-10-01 18:40:57 +05:30
parent 35e1e09401
commit 2a6a201e0b
9 changed files with 150 additions and 83 deletions

View file

@ -1,28 +0,0 @@
import boto3
import json
import logging
from botocore.exceptions import ClientError
logger = logging.getLogger(__name__)
def get_secret(secret_name, region_name="ap-south-2"):
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name
)
try:
get_secret_value_response = client.get_secret_value(SecretId=secret_name)
except ClientError as e:
logger.error(f"Error fetching secret {secret_name}: {e}")
return {}
if 'SecretString' in get_secret_value_response:
secret = get_secret_value_response['SecretString']
try:
return json.loads(secret)
except json.JSONDecodeError:
logger.error(f"Secret {secret_name} is not valid JSON")
return {}
return {}

View file

@ -12,7 +12,6 @@ https://docs.djangoproject.com/en/6.1/ref/settings/
from pathlib import Path
import os
from core.aws_secrets import get_secret
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -93,15 +92,14 @@ if DEBUG:
}
}
else:
db_secret = get_secret("arn:aws:secretsmanager:ap-south-2:764709663363:secret:emailservicebetadb-ZXQg5w")
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': db_secret.get('database', 'emailservice'),
'USER': db_secret.get('username', 'vignesh'),
'PASSWORD': db_secret.get('password', ''),
'HOST': os.environ.get('DB_HOST', db_secret.get('host', '16.113.106.152')),
'PORT': db_secret.get('port', '5432'),
'NAME': os.environ.get('DB_NAME', 'emailservice'),
'USER': os.environ.get('DB_USER', 'vignesh'),
'PASSWORD': os.environ.get('DB_PASSWORD', 'vtechnosoft@123A'),
'HOST': os.environ.get('DB_HOST', '16.113.106.152'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
@ -151,14 +149,16 @@ MEDIA_ROOT = BASE_DIR / 'media'
# Email
# https://docs.djangoproject.com/en/6.1/topics/email/#topic-email-configuration
email_secret = get_secret("arn:aws:secretsmanager:ap-south-2:764709663363:secret:emailcredentials-DOIQtc")
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = email_secret.get('smtp')
EMAIL_PORT = int(email_secret.get('smtp_port', 465) or 465)
EMAIL_HOST = 'smtp.hostinger.com'
EMAIL_PORT = 465
EMAIL_USE_SSL = True
EMAIL_HOST_USER = email_secret.get('username')
EMAIL_HOST_PASSWORD = email_secret.get('password')
EMAIL_HOST_USER = 'info@tradhox.com'
EMAIL_HOST_PASSWORD = ''
# Hostinger Mail API Python SDK Configuration
HOSTINGER_BEARER_TOKEN = '5bee28a2a982c7713f9254014fead914ab6ef18f4cc0685ef1c9919410e9fc82'
HOSTINGER_MAILBOX_RESOURCE_ID = 'ACd8456f8f9be7a07b7f1bccb636d6'
# Logging configuration
import os

18
logs/app.log Normal file
View file

@ -0,0 +1,18 @@
INFO 2026-10-01 18:31:42,978 autoreload 14240 23976 Watching for file changes with StatReloader
INFO 2026-10-01 18:31:53,221 log 14240 10268 "GET / HTTP/1.1" 200 2417
WARNING 2026-10-01 18:31:53,480 log 14240 10268 Not Found: /favicon.ico
WARNING 2026-10-01 18:31:53,481 log 14240 10268 "GET /favicon.ico HTTP/1.1" 404 3318
INFO 2026-10-01 18:31:55,792 log 14240 10268 "GET /test-email/ HTTP/1.1" 200 2688
INFO 2026-10-01 18:32:07,309 services 14240 10268 Hostinger SDK: Attempting to send email to vichunice@gmail.com via mailbox ACd8456f8f9be7a07b7f1bccb636d6
INFO 2026-10-01 18:32:15,970 services 14240 10268 Hostinger SDK: Successfully sent email to vichunice@gmail.com
INFO 2026-10-01 18:32:15,971 views 14240 10268 Successfully sent 'verification' email to vichunice@gmail.com
INFO 2026-10-01 18:32:15,979 log 14240 10268 "POST /test-email/ HTTP/1.1" 302 0
INFO 2026-10-01 18:32:15,982 log 14240 10268 "GET / HTTP/1.1" 200 2417
INFO 2026-10-01 18:32:20,907 log 14240 10268 "GET /logs/ HTTP/1.1" 200 2518
INFO 2026-10-01 18:35:59,965 log 14240 10268 "GET / HTTP/1.1" 200 2417
INFO 2026-10-01 18:36:00,975 log 14240 10268 "GET /test-email/ HTTP/1.1" 200 2906
INFO 2026-10-01 18:36:08,813 services 14240 10268 Hostinger SDK: Attempting to send email to vichunice@gmail.com via mailbox ACd8456f8f9be7a07b7f1bccb636d6
INFO 2026-10-01 18:36:10,222 services 14240 10268 Hostinger SDK: Successfully sent email to vichunice@gmail.com
INFO 2026-10-01 18:36:10,223 views 14240 10268 Successfully sent 'verification' email to vichunice@gmail.com
INFO 2026-10-01 18:36:10,232 log 14240 10268 "POST /test-email/ HTTP/1.1" 302 0
INFO 2026-10-01 18:36:10,235 log 14240 10268 "GET / HTTP/1.1" 200 2417

0
logs/error.log Normal file
View file

View file

@ -1,11 +1,11 @@
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
from .models import EmailLog
from .services import send_email_message
import logging
logger = logging.getLogger(__name__)
@ -41,16 +41,12 @@ class SendEmailAPIView(APIView):
html_content = render_to_string(template_path, context)
text_content = strip_tags(html_content)
msg = EmailMultiAlternatives(
subject,
text_content,
settings.EMAIL_HOST_USER,
[recipient]
send_email_message(
recipient=recipient,
subject=subject,
text_content=text_content,
html_content=html_content
)
msg.attach_alternative(html_content, "text/html")
logger.info(f"API: Attempting to send '{template_name}' email to {recipient} via {getattr(settings, 'EMAIL_HOST', 'unknown')}")
msg.send(fail_silently=False)
logger.info(f"API: Successfully sent '{template_name}' email to {recipient}")
EmailLog.objects.create(

103
mailer/services.py Normal file
View file

@ -0,0 +1,103 @@
import logging
import os
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
import hostinger_mail_api
from hostinger_mail_api import Configuration, ApiClient, SendApi, AccountApi, V1SendRequest, ApiException
logger = logging.getLogger(__name__)
def send_email_via_hostinger(
recipient: str,
subject: str,
text_content: str,
html_content: str = None,
bearer_token: str = None,
mailbox_resource_id: str = None,
display_name: str = None
):
"""
Sends an email using the Hostinger Mail API Python SDK.
"""
token = bearer_token or getattr(settings, 'HOSTINGER_BEARER_TOKEN', '5bee28a2a982c7713f9254014fead914ab6ef18f4cc0685ef1c9919410e9fc82')
if not token:
raise ValueError("Hostinger bearer token is missing. Please configure HOSTINGER_BEARER_TOKEN.")
config = Configuration(access_token=token)
with ApiClient(config) as api_client:
target_mailbox_id = mailbox_resource_id or getattr(settings, 'HOSTINGER_MAILBOX_RESOURCE_ID', 'ACd8456f8f9be7a07b7f1bccb636d6')
# Auto-detect mailbox resource ID if not explicitly provided
if not target_mailbox_id:
try:
account_api = AccountApi(api_client)
account_info = account_api.get_current_account()
if account_info and hasattr(account_info, 'data') and account_info.data:
mailboxes = getattr(account_info.data, 'mailboxes', []) or []
for mb in mailboxes:
sender_address = getattr(settings, 'EMAIL_HOST_USER', '')
if sender_address and getattr(mb, 'address', '') == sender_address:
target_mailbox_id = getattr(mb, 'resource_id', None)
break
if not target_mailbox_id and mailboxes:
target_mailbox_id = getattr(mailboxes[0], 'resource_id', None)
except Exception as e:
logger.warning(f"Hostinger SDK: Could not auto-detect mailbox resource ID: {e}")
if not target_mailbox_id:
raise ValueError("Hostinger Mailbox Resource ID is required.")
send_api = SendApi(api_client)
send_request = V1SendRequest(
to=[recipient],
subject=subject,
text=text_content,
html=html_content or None,
display_name=display_name or getattr(settings, 'EMAIL_DISPLAY_NAME', None)
)
logger.info(f"Hostinger SDK: Attempting to send email to {recipient} via mailbox {target_mailbox_id}")
response = send_api.send_email(
mailbox_resource_id=target_mailbox_id,
v1_send_request=send_request
)
logger.info(f"Hostinger SDK: Successfully sent email to {recipient}")
return response
def send_email_message(
recipient: str,
subject: str,
text_content: str,
html_content: str = None,
display_name: str = None
):
"""
Unified email dispatcher. Uses Hostinger Mail API SDK if BEARER_TOKEN is available,
otherwise falls back to Django SMTP backend.
"""
token = getattr(settings, 'HOSTINGER_BEARER_TOKEN', '5bee28a2a982c7713f9254014fead914ab6ef18f4cc0685ef1c9919410e9fc82')
if token:
return send_email_via_hostinger(
recipient=recipient,
subject=subject,
text_content=text_content,
html_content=html_content,
display_name=display_name
)
# Fallback to standard SMTP via Django core mail
sender = getattr(settings, 'EMAIL_HOST_USER', 'noreply@tradhox.com')
msg = EmailMultiAlternatives(
subject,
text_content,
sender,
[recipient]
)
if html_content:
msg.attach_alternative(html_content, "text/html")
logger.info(f"SMTP Fallback: Sending email to {recipient} via {getattr(settings, 'EMAIL_HOST', 'default SMTP')}")
return msg.send(fail_silently=False)

View file

@ -29,28 +29,12 @@
<!-- Verification Action -->
<tr>
<td align="center" style="padding: 0 32px 32px 32px;">
<div style="background-color: #fbf9f8; border: 1px solid #d9c5b2; border-radius: 4px; padding: 24px; margin-bottom: 32px;">
<div style="background-color: #fbf9f8; border: 1px solid #d9c5b2; border-radius: 4px; padding: 24px;">
<p style="margin: 0 0 16px 0; font-size: 12px; text-transform: uppercase; letter-spacing: 0.1em; color: #636260; font-weight: 600;">Your Verification Code</p>
<div style="font-family: 'Libre Caslon Text', serif; font-size: 40px; letter-spacing: 8px; font-weight: 700; color: #6b1a2c; margin-bottom: 24px;">
<div style="font-family: 'Libre Caslon Text', serif; font-size: 40px; letter-spacing: 8px; font-weight: 700; color: #6b1a2c; margin: 0;">
{{ otp }}
</div>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="40%" style="border-top: 1px solid #d9c5b2;"></td>
<td width="20%" align="center" style="font-size: 12px; color: #636260; text-transform: uppercase; letter-spacing: 0.1em;">OR</td>
<td width="40%" style="border-top: 1px solid #d9c5b2;"></td>
</tr>
</table>
</div>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 4px;" bgcolor="#6b1a2c">
<a href="{{ verification_link }}" target="_blank" style="font-size: 14px; font-weight: 600; font-family: 'Work Sans', sans-serif; color: #ffffff; text-decoration: none; padding: 12px 24px; border-radius: 4px; display: inline-block;">
Verify Email Address &rarr;
</a>
</td>
</tr>
</table>
</td>
</tr>
<!-- Security Notice -->

View file

@ -1,10 +1,10 @@
from django.shortcuts import render, redirect
from django.contrib import messages
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
from .models import EmailLog
from .services import send_email_message
import logging
logger = logging.getLogger(__name__)
@ -45,16 +45,12 @@ def send_test_email(request):
html_content = render_to_string(f'emails/sellercentral/common/{template_name}.html', context)
text_content = strip_tags(html_content)
msg = EmailMultiAlternatives(
subject,
text_content,
settings.EMAIL_HOST_USER,
[recipient]
send_email_message(
recipient=recipient,
subject=subject,
text_content=text_content,
html_content=html_content
)
msg.attach_alternative(html_content, "text/html")
logger.info(f"Attempting to send '{template_name}' test email to {recipient} via {settings.EMAIL_HOST}")
msg.send(fail_silently=False)
logger.info(f"Successfully sent '{template_name}' email to {recipient}")
# Log success

View file

@ -1,16 +1,14 @@
asgiref==3.12.1
boto3==1.43.78
botocore==1.43.78
Django==6.1
djangorestframework==3.18.0
djangorestframework-api-key==3.1.0
jmespath==1.1.0
packaging==26.3
psycopg2-binary==2.9.12
python-dateutil==2.9.0.post0
s3transfer==0.19.2
six==1.17.0
sqlparse==0.6.0
urllib3==2.7.0
gunicorn==22.0.0
whitenoise==6.7.0
hostinger_mail_api==1.18.0
pydantic>=2.0.0