Trying to create simple smtp server

snakeesHOBBY

4 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

snakeesHOBBY

4 months ago

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


4 months ago

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


snakeesHOBBY

4 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)

4 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


snakeesHOBBY

4 months ago

? I have other projects running docker


4 months ago

Docker is fine, not docker compose


snakeesHOBBY

4 months ago

really? because its not crashing and running just fine

1319535981950402600


4 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


4 months ago

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


snakeesHOBBY

4 months ago

got it



snakeesHOBBY

4 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.


4 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


4 months ago

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


snakeesHOBBY

4 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.


4 months ago

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


snakeesHOBBY

4 months ago

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


4 months ago

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


4 months ago

!s


Status changed to Solved adam 4 months ago