Trying to create simple smtp server

snakees
HOBBY

10 months ago

My app only receives emails and stores them in a log file

DNS Config
mx @ mail.domain.com. (Priority: 0)

cname mail foo.up.railway.app.

# Use a base image with Python
FROM python:3.9-slim

# Set environment variable to disable output buffering
ENV PYTHONUNBUFFERED=1

# Set the working directory
WORKDIR /app

# Copy the Python script and dependencies into the container
COPY . .

# Install necessary dependencies
RUN pip install -r requirements.txt

# Expose the ports the apps will run on
EXPOSE 25 8000

# Run both the email service (SMTP) and the Flask app (log-server) using gunicorn
CMD ["sh", "-c", "python -u Inbox.py & gunicorn -w 2 -b 0.0.0.0:8000 Log_server:app"]
services:
  email-service:
    build: .
    ports:
      - "25:25"
    volumes:
      - shared-data:/app/shared

  log-server:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - shared-data:/app/shared

volumes:
  shared-data:

I've ran docker-compose up locally and both the smtp server and the flask server work just fine

import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg.set_content('This is a test email sent from CLI.')
msg['Subject'] = 'Test Email from CLI'
msg['From'] = 'sender@example.com'
msg['To'] = 'receiver@example.com'
smtp = smtplib.SMTP('127.0.0.1', 25)
smtp.send_message(msg)
smtp.quit()

This is the code I used to test the server locally and it worked just fine, but I don't get how I deploy it and how I expose port 25 on mail.domain.com

Solved

0 Replies

snakees
HOBBY

10 months ago

5436c854-e259-4c04-82f4-42aaaf16c454


10 months ago

Railway does not support docker compose, nor does it support running multiple apps within one service


snakees
HOBBY

10 months ago

Inbox.py

import asyncio
from aiosmtpd.controller import Controller
import logging

# Configure logging
logging.basicConfig(level=logging.INFO, filename="./shared/emails.log", filemode="a",
                    format="%(asctime)s - %(message)s")


def log_email(email_content):
    logging.info("Received email:\n%s", email_content)


class EmailHandler:
    async def handle_DATA(self, server, session, envelope):
        try:
            # Decode email content and log it
            email_content = envelope.content.decode('utf-8')
            log_email(email_content)
            print(f"Email received:\n{email_content}")
        except Exception as e:
            print(f"Error processing email: {e}")
        return '250 Message accepted for delivery'


async def main():
    handler = EmailHandler()
    controller = Controller(handler, hostname="0.0.0.0", port=25)

    print("Starting SMTP server on 0.0.0.0:25")
    controller.start()

    try:
        await asyncio.Event().wait()  # Keep the server running
    except KeyboardInterrupt:
        print("Stopping SMTP server...")
    finally:
        controller.stop()


if __name__ == "__main__":
    asyncio.run(main())

Logserver.py

from flask import Flask, Response
import os

# Define the log file path
LOG_FILE_PATH = "./shared/emails.log"

# Initialize the Flask app
app = Flask(__name__)

@app.route('/')
def serve_log_file():
    if os.path.exists(LOG_FILE_PATH):
        # Open the log file and read its content
        with open(LOG_FILE_PATH, 'rb') as log_file:
            log_data = log_file.read()

        # Return the log file as a plain text response
        return Response(log_data, mimetype='text/plain')
    else:
        # If the log file doesn't exist, return a 404
        return "Log file not found.", 404

if __name__ == "__main__":
    # Start the Flask server on port 8000
    app.run(host='0.0.0.0', port=8000)

10 months ago

To make this work, you'll want to separate out your two apps into separate services and have them communicate through the private network


snakees
HOBBY

10 months ago

? I have other projects running docker


10 months ago

Docker is fine, not docker compose


snakees
HOBBY

10 months ago

really? because its not crashing and running just fine

1319535981950402600


10 months ago

Additionally, you may have issues running a mailserver on Railway (at least on non-metal regions) as GCP blocks common mailserver ports and Railway's own proxying may mess with it as well


10 months ago

It may deploy fine, but only one port is properly exposed


snakees
HOBBY

10 months ago

got it



snakees
HOBBY

10 months ago

Ok I wanted to get atleast a poc running so I tweaked the code to only log emails and got rid of the flask app but nothing is still going through. I am wondering how I expose port 25 on my domain instead of 443.


10 months ago

by default, Railway exposes the PORT environment variable. if you need to expose port 25, create a PORT environment variable in your service with the value 25


10 months ago

best to make sure your app is listening on the PORT variable as well rather than hardcoding


snakees
HOBBY

10 months ago

Target port seem to direct traffic from the domain to the port the app uses. eg example.com -> :8000. I am wondering how I can receive traffic from a different port on the public domain eg example.com:25 -> :25.


10 months ago

You do not have control over that. Railway’s proxy handles incoming ports


snakees
HOBBY

10 months ago

Just to confirm you can only get https traffic from custom domains?


10 months ago

correct, you cannot run mail servers because you cannot specifically expose port 25 on tcp


10 months ago

!s


Status changed to Solved adam 10 months ago