برنامه نویسی

سرور SMTP خود را در Go بسازید

Summarize this content to 400 words in Persian Lang
در Valyent، ما در حال ساختن نرم افزار منبع باز برای توسعه دهندگان هستیم.

به عنوان بخشی از این ماموریت، ما در حال توسعه Ferdinand، سرویس ارسال ایمیل خود برای توسعه دهندگان (در حال حاضر در آلفا) هستیم.

زیرساخت ایمیل به چندین پروتکل کلیدی متکی است که مهمترین آنها عبارتند از:

SMTP (پروتکل انتقال پست ساده): برای ارسال و دریافت ایمیل بین سرورهای ایمیل استفاده می شود.

IMAP (پروتکل دسترسی به پیام های اینترنتی): به کاربران اجازه می دهد ایمیل ها را مستقیماً از سرور بخوانند و مدیریت کنند.

POP3 (پروتکل پست آفیس نسخه 3): ایمیل ها را از سرور به دستگاه محلی بارگیری می کند و معمولاً آنها را از سرور حذف می کند.

در مقاله امروز، ما بر روی ساخت خود تمرکز خواهیم کرد سرور SMTP خروجی، منعکس کننده رویکردی است که ما با فردیناند در پیش گرفته ایم. با انجام این کار، درک عمیقی از مهم ترین مؤلفه در زیرساخت ارسال ایمیل به دست خواهیم آورد.

“آنچه را که نمی توانم خلق کنم، نمی فهمم.”

– ریچارد فاینمن

با ساختن یک سرور SMTP خروجی از ابتدا، می توانید سطحی از بینش در مورد تحویل ایمیل به دست آورید که اکثر توسعه دهندگان هرگز به آن دست نمی یابند.

برای ادامه، ما از زبان برنامه نویسی Go به همراه کتابخانه های پست عالی Simon Ser استفاده می کنیم. ما روند را ابهام‌زدایی می‌کنیم، نحوه ارسال ایمیل به سرورهای دیگر را به شما نشان می‌دهیم و حتی مفاهیم کلیدی مانند SPF، DKIM و DMARC را توضیح می‌دهیم که امکان تحویل را فراهم می‌کند.

در پایان، با وجود نداشتن سرور SMTP آماده تولید، حداقل درک عمیق تری از زیرساخت ایمیل خواهید داشت.

درک SMTP: مبانی

قبل از اینکه وارد کد شویم، بیایید بررسی کنیم که SMTP چیست و چگونه کار می کند. SMTP (پروتکل انتقال ایمیل ساده) پروتکل استاندارد برای ارسال ایمیل در سراسر اینترنت است. این یک پروتکل نسبتا ساده و مبتنی بر متن است که بر روی مدل مشتری-سرور عمل می کند.

دستورات SMTP

پروتکل SMTP از دستورات استفاده می کند. هر دستور در SMTP هدف خاصی را در فرآیند ارسال ایمیل انجام می دهد. آنها به سرورها اجازه می دهند خود را معرفی کنند، فرستنده و گیرنده را مشخص کنند، محتوای ایمیل واقعی را انتقال دهند و جلسه ارتباط کلی را مدیریت کنند. به این دستورات به عنوان یک مکالمه ساختاریافته بین دو سرور ایمیل فکر کنید، جایی که هر فرمان بیانگر یک عبارت یا سؤال خاص در آن مکالمه است.

هنگامی که یک سرور SMTP می سازید، اساساً برنامه ای ایجاد می کنید که می تواند به این زبان روان صحبت کند، دستورات دریافتی را تفسیر کند و به درستی پاسخ دهد، و همچنین دستورات مناسب را هنگام ارسال ایمیل صادر کند.

بیایید مهمترین دستورات SMTP را بررسی کنیم تا ببینیم این مکالمه چگونه آشکار می شود:

سلام/چشم (سلام): این دستور مکالمه SMTP را آغاز می کند. EHLO نسخه توسعه یافته SMTP است که از ویژگی های اضافی پشتیبانی می کند. نحو است HELO domain یا EHLO domain. مثلا: EHLO example.com.

ایمیل از: این دستور آدرس ایمیل فرستنده را مشخص می کند و یک تراکنش ایمیل جدید را شروع می کند. از نحو استفاده می کند MAIL FROM:. یک مثال می تواند باشد MAIL FROM:.

RCPT به: برای تعیین آدرس ایمیل گیرنده استفاده می شود، این دستور می تواند چندین بار برای چندین گیرنده استفاده شود. نحو است RCPT TO:. برای مثال: RCPT TO:.

داده ها: این دستور شروع محتوای پیام را نشان می دهد. با خطی که فقط یک نقطه (.) دارد به پایان می رسد. بعد از دستور DATA، محتوای پیام را وارد می‌کنید. مثلا:

DATA
From: john@example.com
To: jane@example.com
Subject: Hello

This is the body of the email.
.

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ترک: این دستور ساده جلسه SMTP را پایان می دهد. نحو آن فقط است QUIT.

RESET (بازنشانی): دستور RSET تراکنش ایمیل فعلی را لغو می کند اما اتصال را باز نگه می دارد. برای شروع مجدد بدون شروع اتصال جدید مفید است. نحو ساده است RSET.

دانشگاه AUTH (Authentication): این دستور برای احراز هویت مشتری به سرور استفاده می شود و مکانیزم های احراز هویت مختلف را پشتیبانی می کند. نحو است AUTH mechanism، مثلا: AUTH LOGIN.

یک مکالمه معمولی SMTP ممکن است به شکل زیر باشد:

C: EHLO client.example.com
S: 250-smtp.example.com Hello client.example.com
S: 250-SIZE 14680064
S: 250-AUTH LOGIN PLAIN
S: 250 HELP

C: MAIL FROM:
S: 250 OK

C: RCPT TO:
S: 250 OK

C: DATA
S: 354 Start mail input; end with .

C: From: sender@example.com
C: To: recipient@example.com
C: Subject: Test Email
C:
C: This is a test email.
C: .

S: 250 OK: queued as 12345

C: QUIT
S: 221 Bye

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

احراز هویت در SMTP

احراز هویت یک جنبه حیاتی SMTP است، به خصوص برای سرورهای ایمیل خروجی. این به جلوگیری از استفاده غیرمجاز از سرور و کاهش هرزنامه کمک می کند. چندین روش احراز هویت در SMTP استفاده می شود:

جلگه: این یک روش احراز هویت ساده است که در آن نام کاربری و رمز عبور به صورت متن واضح ارسال می شود. فقط باید از طریق اتصالات رمزگذاری شده استفاده شود.

وارد شدن: مشابه PLAIN است، اما نام کاربری و رمز عبور در دستورات جداگانه ارسال می شود.

CRAM-MD5: این روش از مکانیزم چالش-پاسخ برای جلوگیری از ارسال رمز عبور به صورت متن شفاف استفاده می کند.

OAUTH2: این روش امکان استفاده از توکن های OAuth 2.0 را برای احراز هویت فراهم می کند.

در اینجا مثالی از نحوه ظاهر احراز هویت PLAIN در مکالمه SMTP آورده شده است:

C: EHLO example.com
S: 250-STARTTLS
S: 250 AUTH PLAIN LOGIN
C: AUTH PLAIN AGVtYWlsQGV4YW1wbGUuY29tAHBhc3N3b3Jk
S: 235 2.7.0 Authentication successful

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این مثال، AGVtYWlsQGV4YW1wbGUuY29tAHBhc3N3b3Jk نسخه کدگذاری شده با base64 است \0email@example.com\0password.

هنگام اجرای احراز هویت در سرور SMTP خود، باید:

روش های تأیید هویت پشتیبانی شده را در پاسخ به دستور EHLO تبلیغ کنید.
کنترل کننده هایی را برای دستور AUTH پیاده سازی کنید که می تواند روش احراز هویت انتخاب شده را پردازش کند.
اعتبار ارائه شده در پایگاه داده کاربر خود را تأیید کنید.
وضعیت احراز هویت را در طول جلسه SMTP حفظ کنید.

حالا بیایید به پیاده سازی این مفاهیم در سرور Go SMTP خود برویم.

دستیابی به قابلیت تحویل: DKIM، SPF، DMARC

تصور کنید نامه ای از طریق خدمات پستی بدون آدرس برگشتی یا مهر رسمی ارسال می کنید. ممکن است به مقصد برسد، اما احتمال زیادی وجود دارد که در انبوه «پست مشکوک» قرار گیرد. در دنیای دیجیتال ایمیل، ما با چالش مشابهی روبرو هستیم.

چگونه اطمینان حاصل کنیم که ایمیل های ما فقط ارسال نمی شوند، بلکه در واقع تحویل داده می شوند و قابل اعتماد هستند؟

تثلیث مقدس احراز هویت ایمیل را وارد کنید: DKIM، SPF و DMARC.

DKIM: امضای دیجیتال ایمیل شما

DKIM (DomainKeys Identified Mail) مانند مهر مومی روی نامه قرون وسطایی است. این ثابت می کند که ایمیل در طول حمل و نقل دستکاری نشده است.

چگونه کار می کند:

سرور ایمیل شما یک امضای دیجیتال به هر ایمیل ارسالی اضافه می کند.
سرور دریافت کننده این امضا را در برابر کلید عمومی منتشر شده در سوابق DNS شما بررسی می کند.
اگر امضا معتبر باشد، ایمیل از بررسی DKIM عبور می کند.

آن را به عنوان پاسپورت ایمیل خود در نظر بگیرید که در هر ایست بازرسی مهر و تایید شده است.

نمونه ضبط DKIM DNS:

._domainkey… IN TXT “v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3QEKyU1fSma0axspqYK5iAj+54lsAg4qRRCnpKK68hawSd8zpsDz77ntGCR0X2mHVvkHbX6dX…oIDAQAB”

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا، 'selector' یک شناسه منحصر به فرد برای این کلید DKIM است، و رشته طولانی کلید عمومی شما است.

SPF: لیست مهمان برای مهمانی دامنه شما

SPF (چارچوب خط‌مشی فرستنده) مانند پرنده در یک باشگاه انحصاری است. مشخص می کند که کدام سرورهای ایمیل مجاز به ارسال ایمیل از طرف دامنه شما هستند.

چگونه کار می کند:

شما فهرستی از آدرس های IP مجاز را در سوابق DNS خود منتشر می کنید.
وقتی ایمیلی می رسد که ادعا می کند از دامنه شماست، سرور دریافت کننده بررسی می کند که آیا از IP موجود در لیست شما آمده است یا خیر.
اگر مطابقت داشته باشد، ایمیل از بررسی SPF عبور می کند.

مثل این است که بگویید: “اگر ایمیل از طرف یکی از این افراد نیامده است، با ما نیست!”

نمونه رکورد SPF DNS:

.. IN TXT “v=spf1 ip4:192.0.2.0/24 include:_spf.google.com ~all”

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این رکورد می گوید:

ایمیل ها می توانند از آدرس های IP در محدوده 192.0.2.0 تا 192.0.2.255 ارسال شوند.
ایمیل ها همچنین می توانند از سرورهای مشخص شده در رکورد SPF گوگل ارسال شوند.
این ~all به معنای شکست نرم ایمیل از منابع دیگر (مشکوک تلقی شود اما رد نشود).

DMARC: سازنده و مجری قانون

DMARC (تأیید هویت، گزارش و انطباق پیام مبتنی بر دامنه) قاضی عاقلانه ای است که تصمیم می گیرد برای ایمیل هایی که بررسی DKIM یا SPF را انجام نمی دهند چه اتفاقی بیفتد.

چگونه کار می کند:

شما در سوابق DNS خود خط‌مشی تنظیم می‌کنید که نحوه مدیریت ایمیل‌هایی که احراز هویت ناموفق هستند را مشخص می‌کند.
گزینه‌ها از «به هر حال اجازه بده» تا «راستی آن را رد کنند» متغیر است.
DMARC همچنین گزارش هایی در مورد نتایج احراز هویت ایمیل ارائه می دهد و به شما کمک می کند تا امنیت ایمیل خود را نظارت کرده و بهبود بخشد.

DMARC را به‌عنوان دفترچه قوانین و گزارش رویداد ایمیل‌های خود در نظر بگیرید.

نمونه رکورد DMARC DNS:

_dmarc… IN TXT “v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@.”

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این رکورد می گوید:

اگر ایمیلی DKIM و SPF را بررسی نکرد، آن را قرنطینه کنید (معمولاً به پوشه هرزنامه ارسال کنید).
گزارش های انبوه در مورد نتایج احراز هویت ایمیل را به dmarc-reports@example.com ارسال کنید.

چرا این تثلیث مهم است

DKIM، SPF و DMARC با هم یک سپر قدرتمند در برابر جعل ایمیل و فیشینگ تشکیل می دهند. آنها به سرورهای دریافت کننده می گویند: “این ایمیل واقعاً از طرف ما است، توسط شخصی که به او اعتماد داریم ارسال شده است، و در اینجا این است که اگر چیزی غیرقابل تحمل به نظر می رسد چه باید کرد.”

اجرای این سه گانه نه تنها قابلیت ارسال ایمیل شما را بهبود می بخشد، بلکه از اعتبار دامنه شما نیز محافظت می کند. این مانند داشتن یک سیستم امنیتی پیشرفته برای زیرساخت ایمیل شماست.

همانطور که ما سرور SMTP خود را می سازیم، در نظر گرفتن این روش های احراز هویت برای اطمینان از اینکه ایمیل های ما فقط ارسال نمی شوند، بلکه در واقع به مقصد می رسند و هنگام رسیدن به آنها اعتماد می شوند، بسیار مهم است. به یاد داشته باشید، هنگام پیاده‌سازی این رکوردها در یک دامنه تولید، با سیاست‌های مجاز شروع کنید و به تدریج آن‌ها را سخت‌تر کنید، زیرا همه چیز درست کار می‌کند.

ساخت سرور SMTP با Go

1. اولیه سازی پروژه

ابتدا، اجازه دهید یک دایرکتوری جدید برای پروژه خود ایجاد کنیم و یک ماژول Go را مقداردهی اولیه کنیم:

mkdir go-smtp-server
cd go-smtp-server
go mod init github.com/yourusername/go-smtp-server

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

2. نصب Dependencies

برای سرور SMTP خود به چند وابستگی نیاز داریم. دستورات زیر را اجرا کنید:

go get github.com/emersion/go-smtp
go get github.com/emersion/go-sasl
go get github.com/emersion/go-msgauth

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

3. راه اندازی اولیه سرور SMTP

یک فایل جدید با نام ایجاد کنید main.go و کد زیر را اضافه کنید:

package main

import (
“log”
“time”
“io”

“github.com/emersion/go-smtp”
)

func main() {
s := smtp.NewServer(&Backend{})

s.Addr = “:2525”
s.Domain = “localhost”
s.WriteTimeout = 10 * time.Second
s.ReadTimeout = 10 * time.Second
s.MaxMessageBytes = 1024 * 1024
s.MaxRecipients = 50
s.AllowInsecureAuth = true

log.Println(“Starting server at”, s.Addr)
if err := s.ListenAndServe(); err != nil {
log.Fatal(err)
}
}

// Backend implements SMTP server methods.
type Backend struct{}

func (bkd *Backend) NewSession(_ *smtp.Conn) (smtp.Session, error) {
return &Session{}, nil
}

// A Session is returned after EHLO.
type Session struct{}

// We’ll implement the Session methods next

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این یک سرور SMTP ایجاد می کند که به پورت 2525 گوش می دهد، یک انتخاب مناسب برای اهداف توسعه، زیرا این پورت برخلاف پورت های استاندارد 25 (SMTP استاندارد)، 465 (TLS)، 587 (STARTTLS) به امتیازات مدیریتی نیاز ندارد.

پیاده سازی EHLO/HELO

فرمان EHLO/HELO به طور خودکار توسط go-smtp کتابخانه نیازی نیست خودمان آن را اجرا کنیم.

پیاده سازی MAIL FROM

این روش را به Session ساختار:

func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
fmt.Println(“Mail from:”, from) s.From = from
return nil
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این روش زمانی فراخوانی می شود که سرور یک فرمان MAIL FROM را دریافت کند. آدرس فرستنده را ثبت کرده و در جلسه ذخیره می کند.

پیاده سازی RCPT TO

این روش را به Session ساختار:

func (s *Session) Rcpt(to string) error {
fmt.Println(“Rcpt to:”, to)
s.To = append(s.To, to)
return nil
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این متد برای هر دستور RCPT TO فراخوانی می شود. آدرس گیرنده را ثبت می کند و به لیست گیرندگان این جلسه اضافه می کند.

پیاده سازی DATA

این روش را به Session ساختار:

import (
“fmt”
“io”
)

func (s *Session) Data(r io.Reader) error {
if b, err := io.ReadAll(r); err != nil {
return err
} else {
fmt.Println(“Received message:”, string(b))

// Here you would typically process the email
return nil
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این روش زمانی فراخوانی می شود که سرور دستور DATA را دریافت کند. کل پیام ایمیل را می خواند و آن را ثبت می کند. در یک سرور واقعی، ایمیل را در اینجا پردازش می کنید.

اجرای AUTH

این روش را به Session ساختار:

func (s *Session) AuthPlain(username, password string) error {
if username != “testuser” || password != “testpass” {
return fmt.Errorf(“Invalid username or password”)
}

return nil
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این یک مکانیسم اصلی احراز هویت را پیاده سازی می کند. توجه داشته باشید که این فقط برای اهداف نمایشی است و نباید در تولید استفاده شود.

پیاده سازی RSET

این روش را به Session ساختار:

func (s *Session) Reset() {
s.From = “” s.To = []string{}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این روش زمانی فراخوانی می شود که سرور دستور RSET را دریافت کند. حالت جلسه را بازنشانی می کند.

اجرای QUIT

این روش را به Session ساختار:

func (s *Session) Logout() error {
return nil
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این روش زمانی فراخوانی می شود که سرور دستور QUIT را دریافت کند. در این پیاده سازی ساده، نیازی به انجام کار خاصی نداریم.

ارسال ایمیل: MX Lookup، Port Selection و DKIM Signing

پس از دریافت و پردازش ایمیل، مرحله بعدی ارسال آن به مقصد است. این شامل دو مرحله کلیدی است: یافتن سرور ایمیل گیرنده با استفاده از رکوردهای MX (مبدل نامه) و تلاش برای ارسال ایمیل با استفاده از پورت های استاندارد SMTP.

ابتدا، اجازه دهید یک تابع برای جستجوی رکوردهای MX اضافه کنیم:

import “net”

func lookupMX(domain string) ([]*net.MX, error) {
mxRecords, err := net.LookupMX(domain)
if err != nil {
return nil, fmt.Errorf(“Error looking up MX records: %v”, err)
}

return mxRecords, nil
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در مرحله بعد، اجازه دهید یک تابع ایجاد کنیم که سعی می کند با استفاده از پورت های مختلف ایمیل ارسال کند:

import (
“crypto/tls”
“net/smtp”
“strings”
)

func sendMail(from string, to string, data []byte) error {
domain := strings.Split(to, “@”)[1]

mxRecords, err := lookupMX(domain)
if err != nil {
return err
}

for _, mx := range mxRecords {
host := mx.Host

for _, port := range []int{25, 587, 465} {
address := fmt.Sprintf(“%s:%d”, host, port)

var c *smtp.Client

var err error

switch port {
case 465:
// SMTPS
tlsConfig := &tls.Config{ServerName: host}
conn, err := tls.Dial(“tcp”, address, tlsConfig)
if err != nil {
continue
}

c, err = smtp.NewClient(conn, host)

case 25, 587:
// SMTP or SMTP with STARTTLS
c, err = smtp.Dial(address)
if err != nil {
continue
}

if port == 587 {
if err = c.StartTLS(&tls.Config{ServerName: host}); err != nil {
c.Close()
continue
}
}
}

if err != nil {
continue
}

// SMTP conversation
if err = c.Mail(from); err != nil {
c.Close()
continue
}

if err = c.Rcpt(to); err != nil {
c.Close()
continue
}

w, err := c.Data()
if err != nil {
c.Close()
continue
}

if _, err := w.Write(data); err != nil {
c.Close()
continue
}

err = w.Close()
if err != nil {
c.Close()
continue
}

c.Quit()

return nil
}
}

return fmt.Errorf(“Failed to send email to %s”, to)
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این تابع کارهای زیر را انجام می دهد:

رکوردهای MX را برای دامنه گیرنده جستجو می کند.
برای هر رکورد MX، سعی می‌کند با استفاده از پورت‌های 25، 587 و 465 به آن ترتیب متصل شود.
از روش اتصال مناسب برای هر پورت استفاده می کند:

پورت 25: SMTP ساده
پورت 587: SMTP با STARTTLS
پورت 465: SMTPS (SMTP بیش از TLS)

در صورت موفقیت آمیز بودن اتصال، سعی می کند ایمیل را با استفاده از پروتکل SMTP ارسال کند.

اگر ایمیل با موفقیت ارسال شود، باز می گردد. در غیر این صورت، پورت بعدی یا رکورد MX را امتحان می کند.

حالا بیایید خودمان را اصلاح کنیم Data روش در Session struct برای استفاده از این جدید sendMail تابع:

func (s *Session) Data(r io.Reader) error {
if data, err := io.ReadAll(r); err != nil {
return err
} else {
fmt.Println(“Received message:”, string(data))
for _, recipient := range s.To {
if err := sendMail(s.From, recipient, data); err != nil {
fmt.Printf(“Failed to send email to %s: %v”, recipient, err)
} else {
fmt.Printf(“Email sent successfully to %s”, recipient)
}

}

return nil
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این پیاده سازی سعی می کند ایمیل های دریافتی را با استفاده از سرور ایمیل و پورت مناسب برای هر گیرنده ارسال کند.

اکنون، اجازه دهید امضای DKIM را به فرآیند ارسال ایمیل خود اضافه کنیم. ابتدا باید بسته های لازم را وارد کنیم و گزینه های DKIM خود را راه اندازی کنیم:

import (
// … other imports …
“crypto/rsa”
“crypto/x509”
“encoding/pem”
“github.com/emersion/go-msgauth/dkim”
)

// Load your DKIM private key
var dkimPrivateKey *rsa.PrivateKey

func init() {
// Load your DKIM private key from a file
privateKeyPEM, err := ioutil.ReadFile(“path/to/your/private_key.pem”)
if err != nil {
log.Fatalf(“Failed to read private key: %v”, err)
}

block, _ := pem.Decode(privateKeyPEM)
if block == nil {
log.Fatalf(“Failed to parse PEM block containing the private key”)
}

privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
log.Fatalf(“Failed to parse private key: %v”, err)
}

dkimPrivateKey = privateKey
}

// DKIM options
var dkimOptions = &dkim.SignOptions{
Domain: “example.com”,
Selector: “default”,
Signer: dkimPrivateKey,
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بعد، بیایید خود را اصلاح کنیم sendMail عملکرد شامل امضای DKIM:

func sendMail(from string, to string, data []byte) error {
// … [previous MX lookup code] …

for _, mx := range mxRecords {
host := mx.Host
for _, port := range []int{25, 587, 465} {
// … [previous connection code] …

// DKIM sign the message
var b bytes.Buffer
if err := dkim.Sign(&b, bytes.NewReader(data), dkimOptions); err != nil {
return fmt.Errorf(“Failed to sign email with DKIM: %v”, err)
}
signedData := b.Bytes()

// SMTP conversation
if err = c.Mail(from); err != nil {
c.Close()
continue
}
if err = c.Rcpt(to); err != nil {
c.Close()
continue
}
w, err := c.Data()
if err != nil {
c.Close()
continue
}
_, err = w.Write(signedData) // Use the DKIM signed message
if err != nil {
c.Close()
continue
}
err = w.Close()
if err != nil {
c.Close()
continue
}
c.Quit()
return nil
}
}

return fmt.Errorf(“Failed to send email to %s”, to)
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این به روز شده است sendMail تابع:

قبل از ارسال، داده‌های ایمیل را با DKIM امضا می‌کنیم.
ما از داده های امضا شده استفاده می کنیم (signedData) هنگام نوشتن در اتصال SMTP.

این پیاده سازی یک امضای DKIM را به ایمیل های خروجی شما اضافه می کند که به بهبود قابلیت تحویل و صحت ایمیل های شما کمک می کند.

به یاد داشته باشید که جایگزین کنید “path/to/your/private_key.pem” با مسیر واقعی کلید خصوصی DKIM خود، و به روز رسانی کنید Domain و Selector که در dkimOptions برای مطابقت با رکورد DKIM DNS شما.

ملاحظات و مراحل بعدی

در حالی که این پیاده سازی یک سرور SMTP کار پایه را فراهم می کند که قادر به دریافت و ارسال ایمیل است، چندین ملاحظات مهم برای سرور آماده تولید وجود دارد:

محدود کردن نرخ: برای جلوگیری از سوء استفاده و محافظت در برابر بمب گذاری ایمیل، محدودیت نرخ را اعمال کنید.

جلوگیری از اسپم: اقداماتی را برای جلوگیری از استفاده از سرور خود برای ارسال هرزنامه انجام دهید.

رسیدگی به خطا: برای رفع اشکال و نظارت بهتر، مدیریت و ثبت خطا را بهبود بخشید.

مدیریت صف: زمانی که ایمیل ها ارسال نشدند، یک سیستم صف برای منطق امتحان مجدد اجرا کنید.

نتیجه

امیدواریم با خواندن این پست چیزهای زیادی یاد گرفته باشید. برای کسب اطلاعات بیشتر در مورد ارسال ایمیل، به راحتی نگاهی به مخزن GitHub فردیناند بیندازید و کد را بررسی کنید.

در Valyent، ما در حال ساختن نرم افزار منبع باز برای توسعه دهندگان هستیم.

به عنوان بخشی از این ماموریت، ما در حال توسعه Ferdinand، سرویس ارسال ایمیل خود برای توسعه دهندگان (در حال حاضر در آلفا) هستیم.

زیرساخت ایمیل به چندین پروتکل کلیدی متکی است که مهمترین آنها عبارتند از:

  1. SMTP (پروتکل انتقال پست ساده): برای ارسال و دریافت ایمیل بین سرورهای ایمیل استفاده می شود.
  2. IMAP (پروتکل دسترسی به پیام های اینترنتی): به کاربران اجازه می دهد ایمیل ها را مستقیماً از سرور بخوانند و مدیریت کنند.
  3. POP3 (پروتکل پست آفیس نسخه 3): ایمیل ها را از سرور به دستگاه محلی بارگیری می کند و معمولاً آنها را از سرور حذف می کند.

در مقاله امروز، ما بر روی ساخت خود تمرکز خواهیم کرد سرور SMTP خروجی، منعکس کننده رویکردی است که ما با فردیناند در پیش گرفته ایم. با انجام این کار، درک عمیقی از مهم ترین مؤلفه در زیرساخت ارسال ایمیل به دست خواهیم آورد.

“آنچه را که نمی توانم خلق کنم، نمی فهمم.”

– ریچارد فاینمن

با ساختن یک سرور SMTP خروجی از ابتدا، می توانید سطحی از بینش در مورد تحویل ایمیل به دست آورید که اکثر توسعه دهندگان هرگز به آن دست نمی یابند.

برای ادامه، ما از زبان برنامه نویسی Go به همراه کتابخانه های پست عالی Simon Ser استفاده می کنیم. ما روند را ابهام‌زدایی می‌کنیم، نحوه ارسال ایمیل به سرورهای دیگر را به شما نشان می‌دهیم و حتی مفاهیم کلیدی مانند SPF، DKIM و DMARC را توضیح می‌دهیم که امکان تحویل را فراهم می‌کند.

در پایان، با وجود نداشتن سرور SMTP آماده تولید، حداقل درک عمیق تری از زیرساخت ایمیل خواهید داشت.

درک SMTP: مبانی

قبل از اینکه وارد کد شویم، بیایید بررسی کنیم که SMTP چیست و چگونه کار می کند. SMTP (پروتکل انتقال ایمیل ساده) پروتکل استاندارد برای ارسال ایمیل در سراسر اینترنت است. این یک پروتکل نسبتا ساده و مبتنی بر متن است که بر روی مدل مشتری-سرور عمل می کند.

دستورات SMTP

پروتکل SMTP از دستورات استفاده می کند. هر دستور در SMTP هدف خاصی را در فرآیند ارسال ایمیل انجام می دهد. آنها به سرورها اجازه می دهند خود را معرفی کنند، فرستنده و گیرنده را مشخص کنند، محتوای ایمیل واقعی را انتقال دهند و جلسه ارتباط کلی را مدیریت کنند. به این دستورات به عنوان یک مکالمه ساختاریافته بین دو سرور ایمیل فکر کنید، جایی که هر فرمان بیانگر یک عبارت یا سؤال خاص در آن مکالمه است.

هنگامی که یک سرور SMTP می سازید، اساساً برنامه ای ایجاد می کنید که می تواند به این زبان روان صحبت کند، دستورات دریافتی را تفسیر کند و به درستی پاسخ دهد، و همچنین دستورات مناسب را هنگام ارسال ایمیل صادر کند.

بیایید مهمترین دستورات SMTP را بررسی کنیم تا ببینیم این مکالمه چگونه آشکار می شود:

  • سلام/چشم (سلام): این دستور مکالمه SMTP را آغاز می کند. EHLO نسخه توسعه یافته SMTP است که از ویژگی های اضافی پشتیبانی می کند. نحو است HELO domain یا EHLO domain. مثلا: EHLO example.com.
  • ایمیل از: این دستور آدرس ایمیل فرستنده را مشخص می کند و یک تراکنش ایمیل جدید را شروع می کند. از نحو استفاده می کند MAIL FROM:. یک مثال می تواند باشد MAIL FROM:.
  • RCPT به: برای تعیین آدرس ایمیل گیرنده استفاده می شود، این دستور می تواند چندین بار برای چندین گیرنده استفاده شود. نحو است RCPT TO:. برای مثال: RCPT TO:.
  • داده ها: این دستور شروع محتوای پیام را نشان می دهد. با خطی که فقط یک نقطه (.) دارد به پایان می رسد. بعد از دستور DATA، محتوای پیام را وارد می‌کنید. مثلا:
DATA
From: john@example.com
To: jane@example.com
Subject: Hello

This is the body of the email.
.
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • ترک: این دستور ساده جلسه SMTP را پایان می دهد. نحو آن فقط است QUIT.
  • RESET (بازنشانی): دستور RSET تراکنش ایمیل فعلی را لغو می کند اما اتصال را باز نگه می دارد. برای شروع مجدد بدون شروع اتصال جدید مفید است. نحو ساده است RSET.
  • دانشگاه AUTH (Authentication): این دستور برای احراز هویت مشتری به سرور استفاده می شود و مکانیزم های احراز هویت مختلف را پشتیبانی می کند. نحو است AUTH mechanism، مثلا: AUTH LOGIN.

یک مکالمه معمولی SMTP ممکن است به شکل زیر باشد:

C: EHLO client.example.com
S: 250-smtp.example.com Hello client.example.com
S: 250-SIZE 14680064
S: 250-AUTH LOGIN PLAIN
S: 250 HELP

C: MAIL FROM:
S: 250 OK

C: RCPT TO:
S: 250 OK

C: DATA
S: 354 Start mail input; end with .

C: From: sender@example.com
C: To: recipient@example.com
C: Subject: Test Email
C:
C: This is a test email.
C: .

S: 250 OK: queued as 12345

C: QUIT
S: 221 Bye
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

احراز هویت در SMTP

احراز هویت یک جنبه حیاتی SMTP است، به خصوص برای سرورهای ایمیل خروجی. این به جلوگیری از استفاده غیرمجاز از سرور و کاهش هرزنامه کمک می کند. چندین روش احراز هویت در SMTP استفاده می شود:

  1. جلگه: این یک روش احراز هویت ساده است که در آن نام کاربری و رمز عبور به صورت متن واضح ارسال می شود. فقط باید از طریق اتصالات رمزگذاری شده استفاده شود.
  2. وارد شدن: مشابه PLAIN است، اما نام کاربری و رمز عبور در دستورات جداگانه ارسال می شود.
  3. CRAM-MD5: این روش از مکانیزم چالش-پاسخ برای جلوگیری از ارسال رمز عبور به صورت متن شفاف استفاده می کند.
  4. OAUTH2: این روش امکان استفاده از توکن های OAuth 2.0 را برای احراز هویت فراهم می کند.

در اینجا مثالی از نحوه ظاهر احراز هویت PLAIN در مکالمه SMTP آورده شده است:

C: EHLO example.com
S: 250-STARTTLS
S: 250 AUTH PLAIN LOGIN
C: AUTH PLAIN AGVtYWlsQGV4YW1wbGUuY29tAHBhc3N3b3Jk
S: 235 2.7.0 Authentication successful
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این مثال، AGVtYWlsQGV4YW1wbGUuY29tAHBhc3N3b3Jk نسخه کدگذاری شده با base64 است \0email@example.com\0password.

هنگام اجرای احراز هویت در سرور SMTP خود، باید:

  1. روش های تأیید هویت پشتیبانی شده را در پاسخ به دستور EHLO تبلیغ کنید.
  2. کنترل کننده هایی را برای دستور AUTH پیاده سازی کنید که می تواند روش احراز هویت انتخاب شده را پردازش کند.
  3. اعتبار ارائه شده در پایگاه داده کاربر خود را تأیید کنید.
  4. وضعیت احراز هویت را در طول جلسه SMTP حفظ کنید.

حالا بیایید به پیاده سازی این مفاهیم در سرور Go SMTP خود برویم.

دستیابی به قابلیت تحویل: DKIM، SPF، DMARC

تصور کنید نامه ای از طریق خدمات پستی بدون آدرس برگشتی یا مهر رسمی ارسال می کنید. ممکن است به مقصد برسد، اما احتمال زیادی وجود دارد که در انبوه «پست مشکوک» قرار گیرد. در دنیای دیجیتال ایمیل، ما با چالش مشابهی روبرو هستیم.

چگونه اطمینان حاصل کنیم که ایمیل های ما فقط ارسال نمی شوند، بلکه در واقع تحویل داده می شوند و قابل اعتماد هستند؟

تثلیث مقدس احراز هویت ایمیل را وارد کنید: DKIM، SPF و DMARC.

DKIM: امضای دیجیتال ایمیل شما

DKIM (DomainKeys Identified Mail) مانند مهر مومی روی نامه قرون وسطایی است. این ثابت می کند که ایمیل در طول حمل و نقل دستکاری نشده است.

چگونه کار می کند:

  • سرور ایمیل شما یک امضای دیجیتال به هر ایمیل ارسالی اضافه می کند.
  • سرور دریافت کننده این امضا را در برابر کلید عمومی منتشر شده در سوابق DNS شما بررسی می کند.
  • اگر امضا معتبر باشد، ایمیل از بررسی DKIM عبور می کند.

آن را به عنوان پاسپورت ایمیل خود در نظر بگیرید که در هر ایست بازرسی مهر و تایید شده است.

نمونه ضبط DKIM DNS:

._domainkey... IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3QEKyU1fSma0axspqYK5iAj+54lsAg4qRRCnpKK68hawSd8zpsDz77ntGCR0X2mHVvkHbX6dX...oIDAQAB"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا، 'selector' یک شناسه منحصر به فرد برای این کلید DKIM است، و رشته طولانی کلید عمومی شما است.

SPF: لیست مهمان برای مهمانی دامنه شما

SPF (چارچوب خط‌مشی فرستنده) مانند پرنده در یک باشگاه انحصاری است. مشخص می کند که کدام سرورهای ایمیل مجاز به ارسال ایمیل از طرف دامنه شما هستند.

چگونه کار می کند:

  • شما فهرستی از آدرس های IP مجاز را در سوابق DNS خود منتشر می کنید.
  • وقتی ایمیلی می رسد که ادعا می کند از دامنه شماست، سرور دریافت کننده بررسی می کند که آیا از IP موجود در لیست شما آمده است یا خیر.
  • اگر مطابقت داشته باشد، ایمیل از بررسی SPF عبور می کند.

مثل این است که بگویید: “اگر ایمیل از طرف یکی از این افراد نیامده است، با ما نیست!”

نمونه رکورد SPF DNS:

.. IN TXT "v=spf1 ip4:192.0.2.0/24 include:_spf.google.com ~all"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این رکورد می گوید:

  • ایمیل ها می توانند از آدرس های IP در محدوده 192.0.2.0 تا 192.0.2.255 ارسال شوند.
  • ایمیل ها همچنین می توانند از سرورهای مشخص شده در رکورد SPF گوگل ارسال شوند.
  • این ~all به معنای شکست نرم ایمیل از منابع دیگر (مشکوک تلقی شود اما رد نشود).

DMARC: سازنده و مجری قانون

DMARC (تأیید هویت، گزارش و انطباق پیام مبتنی بر دامنه) قاضی عاقلانه ای است که تصمیم می گیرد برای ایمیل هایی که بررسی DKIM یا SPF را انجام نمی دهند چه اتفاقی بیفتد.

چگونه کار می کند:

  • شما در سوابق DNS خود خط‌مشی تنظیم می‌کنید که نحوه مدیریت ایمیل‌هایی که احراز هویت ناموفق هستند را مشخص می‌کند.
  • گزینه‌ها از «به هر حال اجازه بده» تا «راستی آن را رد کنند» متغیر است.
  • DMARC همچنین گزارش هایی در مورد نتایج احراز هویت ایمیل ارائه می دهد و به شما کمک می کند تا امنیت ایمیل خود را نظارت کرده و بهبود بخشد.

DMARC را به‌عنوان دفترچه قوانین و گزارش رویداد ایمیل‌های خود در نظر بگیرید.

نمونه رکورد DMARC DNS:

_dmarc... IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@."
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این رکورد می گوید:

  • اگر ایمیلی DKIM و SPF را بررسی نکرد، آن را قرنطینه کنید (معمولاً به پوشه هرزنامه ارسال کنید).
  • گزارش های انبوه در مورد نتایج احراز هویت ایمیل را به dmarc-reports@example.com ارسال کنید.

چرا این تثلیث مهم است

DKIM، SPF و DMARC با هم یک سپر قدرتمند در برابر جعل ایمیل و فیشینگ تشکیل می دهند. آنها به سرورهای دریافت کننده می گویند: “این ایمیل واقعاً از طرف ما است، توسط شخصی که به او اعتماد داریم ارسال شده است، و در اینجا این است که اگر چیزی غیرقابل تحمل به نظر می رسد چه باید کرد.”

اجرای این سه گانه نه تنها قابلیت ارسال ایمیل شما را بهبود می بخشد، بلکه از اعتبار دامنه شما نیز محافظت می کند. این مانند داشتن یک سیستم امنیتی پیشرفته برای زیرساخت ایمیل شماست.

همانطور که ما سرور SMTP خود را می سازیم، در نظر گرفتن این روش های احراز هویت برای اطمینان از اینکه ایمیل های ما فقط ارسال نمی شوند، بلکه در واقع به مقصد می رسند و هنگام رسیدن به آنها اعتماد می شوند، بسیار مهم است. به یاد داشته باشید، هنگام پیاده‌سازی این رکوردها در یک دامنه تولید، با سیاست‌های مجاز شروع کنید و به تدریج آن‌ها را سخت‌تر کنید، زیرا همه چیز درست کار می‌کند.

ساخت سرور SMTP با Go

1. اولیه سازی پروژه

ابتدا، اجازه دهید یک دایرکتوری جدید برای پروژه خود ایجاد کنیم و یک ماژول Go را مقداردهی اولیه کنیم:

mkdir go-smtp-server
cd go-smtp-server
go mod init github.com/yourusername/go-smtp-server
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

2. نصب Dependencies

برای سرور SMTP خود به چند وابستگی نیاز داریم. دستورات زیر را اجرا کنید:

go get github.com/emersion/go-smtp
go get github.com/emersion/go-sasl
go get github.com/emersion/go-msgauth
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

3. راه اندازی اولیه سرور SMTP

  1. یک فایل جدید با نام ایجاد کنید main.go و کد زیر را اضافه کنید:
package main

import (
    "log"
    "time"
    "io"

    "github.com/emersion/go-smtp"
)

func main() {
    s := smtp.NewServer(&Backend{})

    s.Addr = ":2525"
    s.Domain = "localhost"
    s.WriteTimeout = 10 * time.Second
    s.ReadTimeout = 10 * time.Second
    s.MaxMessageBytes = 1024 * 1024
    s.MaxRecipients = 50
    s.AllowInsecureAuth = true

    log.Println("Starting server at", s.Addr)
    if err := s.ListenAndServe(); err != nil {
        log.Fatal(err)
    }
}

// Backend implements SMTP server methods.
type Backend struct{}

func (bkd *Backend) NewSession(_ *smtp.Conn) (smtp.Session, error) {
    return &Session{}, nil
}

// A Session is returned after EHLO.
type Session struct{}

// We'll implement the Session methods next
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این یک سرور SMTP ایجاد می کند که به پورت 2525 گوش می دهد، یک انتخاب مناسب برای اهداف توسعه، زیرا این پورت برخلاف پورت های استاندارد 25 (SMTP استاندارد)، 465 (TLS)، 587 (STARTTLS) به امتیازات مدیریتی نیاز ندارد.

  1. پیاده سازی EHLO/HELO

فرمان EHLO/HELO به طور خودکار توسط go-smtp کتابخانه نیازی نیست خودمان آن را اجرا کنیم.

  1. پیاده سازی MAIL FROM

این روش را به Session ساختار:

func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
    fmt.Println("Mail from:", from) s.From = from
    return nil
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این روش زمانی فراخوانی می شود که سرور یک فرمان MAIL FROM را دریافت کند. آدرس فرستنده را ثبت کرده و در جلسه ذخیره می کند.

  1. پیاده سازی RCPT TO

این روش را به Session ساختار:

func (s *Session) Rcpt(to string) error {
    fmt.Println("Rcpt to:", to)
    s.To = append(s.To, to)
    return nil
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این متد برای هر دستور RCPT TO فراخوانی می شود. آدرس گیرنده را ثبت می کند و به لیست گیرندگان این جلسه اضافه می کند.

  1. پیاده سازی DATA

این روش را به Session ساختار:

import (
    "fmt"
    "io"
)

func (s *Session) Data(r io.Reader) error {
    if b, err := io.ReadAll(r); err != nil {
        return err
    } else {
        fmt.Println("Received message:", string(b))

        // Here you would typically process the email
        return nil
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این روش زمانی فراخوانی می شود که سرور دستور DATA را دریافت کند. کل پیام ایمیل را می خواند و آن را ثبت می کند. در یک سرور واقعی، ایمیل را در اینجا پردازش می کنید.

  1. اجرای AUTH

این روش را به Session ساختار:

func (s *Session) AuthPlain(username, password string) error {
    if username != "testuser" || password != "testpass" {
        return fmt.Errorf("Invalid username or password")
    }

    return nil
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این یک مکانیسم اصلی احراز هویت را پیاده سازی می کند. توجه داشته باشید که این فقط برای اهداف نمایشی است و نباید در تولید استفاده شود.

  1. پیاده سازی RSET

این روش را به Session ساختار:

func (s *Session) Reset() {
    s.From = "" s.To = []string{}
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این روش زمانی فراخوانی می شود که سرور دستور RSET را دریافت کند. حالت جلسه را بازنشانی می کند.

  1. اجرای QUIT

این روش را به Session ساختار:

func (s *Session) Logout() error {
    return nil
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این روش زمانی فراخوانی می شود که سرور دستور QUIT را دریافت کند. در این پیاده سازی ساده، نیازی به انجام کار خاصی نداریم.

  1. ارسال ایمیل: MX Lookup، Port Selection و DKIM Signing

پس از دریافت و پردازش ایمیل، مرحله بعدی ارسال آن به مقصد است. این شامل دو مرحله کلیدی است: یافتن سرور ایمیل گیرنده با استفاده از رکوردهای MX (مبدل نامه) و تلاش برای ارسال ایمیل با استفاده از پورت های استاندارد SMTP.

ابتدا، اجازه دهید یک تابع برای جستجوی رکوردهای MX اضافه کنیم:

import "net"

func lookupMX(domain string) ([]*net.MX, error) {
    mxRecords, err := net.LookupMX(domain)
    if err != nil {
        return nil, fmt.Errorf("Error looking up MX records: %v", err)
    }

    return mxRecords, nil
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در مرحله بعد، اجازه دهید یک تابع ایجاد کنیم که سعی می کند با استفاده از پورت های مختلف ایمیل ارسال کند:

import (
    "crypto/tls"
    "net/smtp"
    "strings"
)

func sendMail(from string, to string, data []byte) error {
    domain := strings.Split(to, "@")[1]

    mxRecords, err := lookupMX(domain)
    if err != nil {
        return err
    }

    for _, mx := range mxRecords {
        host := mx.Host

        for _, port := range []int{25, 587, 465} {
            address := fmt.Sprintf("%s:%d", host, port)

            var c *smtp.Client

            var err error

            switch port {
            case 465:
                // SMTPS
                tlsConfig := &tls.Config{ServerName: host}
                conn, err := tls.Dial("tcp", address, tlsConfig)
                if err != nil {
                    continue
                }

                c, err = smtp.NewClient(conn, host)

            case 25, 587:
                // SMTP or SMTP with STARTTLS
                c, err = smtp.Dial(address)
                if err != nil {
                    continue
                }

                if port == 587 {
                    if err = c.StartTLS(&tls.Config{ServerName: host}); err != nil {
                        c.Close()
                        continue
                    }
                }
            }

            if err != nil {
                continue
            }

            // SMTP conversation
            if err = c.Mail(from); err != nil {
                c.Close()
                continue
            }

            if err = c.Rcpt(to); err != nil {
                c.Close()
                continue
            }

            w, err := c.Data()
            if err != nil {
                c.Close()
                continue
            }

            if _, err := w.Write(data); err != nil {
                c.Close()
                continue
            }

            err = w.Close()
            if err != nil {
                c.Close()
                continue
            }

            c.Quit()

            return nil
        }
    }

    return fmt.Errorf("Failed to send email to %s", to)
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این تابع کارهای زیر را انجام می دهد:

  • رکوردهای MX را برای دامنه گیرنده جستجو می کند.
  • برای هر رکورد MX، سعی می‌کند با استفاده از پورت‌های 25، 587 و 465 به آن ترتیب متصل شود.
  • از روش اتصال مناسب برای هر پورت استفاده می کند:
    • پورت 25: SMTP ساده
    • پورت 587: SMTP با STARTTLS
    • پورت 465: SMTPS (SMTP بیش از TLS)
  • در صورت موفقیت آمیز بودن اتصال، سعی می کند ایمیل را با استفاده از پروتکل SMTP ارسال کند.
  • اگر ایمیل با موفقیت ارسال شود، باز می گردد. در غیر این صورت، پورت بعدی یا رکورد MX را امتحان می کند.

حالا بیایید خودمان را اصلاح کنیم Data روش در Session struct برای استفاده از این جدید sendMail تابع:

func (s *Session) Data(r io.Reader) error {
    if data, err := io.ReadAll(r); err != nil {
        return err
    } else {
        fmt.Println("Received message:", string(data))
        for _, recipient := range s.To {
            if err := sendMail(s.From, recipient, data); err != nil {
                fmt.Printf("Failed to send email to %s: %v", recipient, err)
            } else {
                fmt.Printf("Email sent successfully to %s", recipient)
            }

        }

        return nil
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این پیاده سازی سعی می کند ایمیل های دریافتی را با استفاده از سرور ایمیل و پورت مناسب برای هر گیرنده ارسال کند.

اکنون، اجازه دهید امضای DKIM را به فرآیند ارسال ایمیل خود اضافه کنیم. ابتدا باید بسته های لازم را وارد کنیم و گزینه های DKIM خود را راه اندازی کنیم:

import (
    // ... other imports ...
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "github.com/emersion/go-msgauth/dkim"
)

// Load your DKIM private key
var dkimPrivateKey *rsa.PrivateKey

func init() {
    // Load your DKIM private key from a file
    privateKeyPEM, err := ioutil.ReadFile("path/to/your/private_key.pem")
    if err != nil {
        log.Fatalf("Failed to read private key: %v", err)
    }

    block, _ := pem.Decode(privateKeyPEM)
    if block == nil {
        log.Fatalf("Failed to parse PEM block containing the private key")
    }

    privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err != nil {
        log.Fatalf("Failed to parse private key: %v", err)
    }

    dkimPrivateKey = privateKey
}

// DKIM options
var dkimOptions = &dkim.SignOptions{
    Domain: "example.com",
    Selector: "default",
    Signer: dkimPrivateKey,
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بعد، بیایید خود را اصلاح کنیم sendMail عملکرد شامل امضای DKIM:

func sendMail(from string, to string, data []byte) error {
    // ... [previous MX lookup code] ...

    for _, mx := range mxRecords {
        host := mx.Host
        for _, port := range []int{25, 587, 465} {
            // ... [previous connection code] ...

            // DKIM sign the message
            var b bytes.Buffer
            if err := dkim.Sign(&b, bytes.NewReader(data), dkimOptions); err != nil {
                return fmt.Errorf("Failed to sign email with DKIM: %v", err)
            }
            signedData := b.Bytes()

            // SMTP conversation
            if err = c.Mail(from); err != nil {
                c.Close()
                continue
            }
            if err = c.Rcpt(to); err != nil {
                c.Close()
                continue
            }
            w, err := c.Data()
            if err != nil {
                c.Close()
                continue
            }
            _, err = w.Write(signedData) // Use the DKIM signed message
            if err != nil {
                c.Close()
                continue
            }
            err = w.Close()
            if err != nil {
                c.Close()
                continue
            }
            c.Quit()
            return nil
        }
    }

    return fmt.Errorf("Failed to send email to %s", to)
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این به روز شده است sendMail تابع:

  • قبل از ارسال، داده‌های ایمیل را با DKIM امضا می‌کنیم.
  • ما از داده های امضا شده استفاده می کنیم (signedData) هنگام نوشتن در اتصال SMTP.

این پیاده سازی یک امضای DKIM را به ایمیل های خروجی شما اضافه می کند که به بهبود قابلیت تحویل و صحت ایمیل های شما کمک می کند.

به یاد داشته باشید که جایگزین کنید "path/to/your/private_key.pem" با مسیر واقعی کلید خصوصی DKIM خود، و به روز رسانی کنید Domain و Selector که در dkimOptions برای مطابقت با رکورد DKIM DNS شما.

  1. ملاحظات و مراحل بعدی

در حالی که این پیاده سازی یک سرور SMTP کار پایه را فراهم می کند که قادر به دریافت و ارسال ایمیل است، چندین ملاحظات مهم برای سرور آماده تولید وجود دارد:

  • محدود کردن نرخ: برای جلوگیری از سوء استفاده و محافظت در برابر بمب گذاری ایمیل، محدودیت نرخ را اعمال کنید.
  • جلوگیری از اسپم: اقداماتی را برای جلوگیری از استفاده از سرور خود برای ارسال هرزنامه انجام دهید.
  • رسیدگی به خطا: برای رفع اشکال و نظارت بهتر، مدیریت و ثبت خطا را بهبود بخشید.
  • مدیریت صف: زمانی که ایمیل ها ارسال نشدند، یک سیستم صف برای منطق امتحان مجدد اجرا کنید.

نتیجه

امیدواریم با خواندن این پست چیزهای زیادی یاد گرفته باشید. برای کسب اطلاعات بیشتر در مورد ارسال ایمیل، به راحتی نگاهی به مخزن GitHub فردیناند بیندازید و کد را بررسی کنید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا