Перейти к содержанию

Echo

Инструкция для приложений на Echo (Go 1.22+). Mailexam® подключается как SMTP через стандартную библиотеку net/smtp с STARTTLS на порту 587.

Что понадобится

  • Аккаунт Mailexam® и проект с SMTP-учетными данными.
  • Go 1.22+.

Скопируйте из приветственного письма (или личного кабинета) для вашего проекта:

  • ВАШ_ЛОГИН — SMTP-логин (например, xxxxx);
  • ВАШ_ПАРОЛЬ — SMTP-пароль (уникальная пара к логину);
  • хост — ВАШ_ЛОГИН.mailexam.ru (совпадает с логином).

1. Зависимости

go mod init example.com/mailexam-echo
go get github.com/labstack/echo/v4@v4.13.3
go get github.com/joho/godotenv@v1.5.1

Файл go.mod (версии подтянутся после go get):

module example.com/mailexam-echo

go 1.22

require (
    github.com/joho/godotenv v1.5.1
    github.com/labstack/echo/v4 v4.13.3
)

2. Переменные окружения

Файл .env в корне проекта (не коммитьте пароли в git):

MAILEXAM_LOGIN=ВАШ_ЛОГИН
MAILEXAM_PASSWORD=ВАШ_ПАРОЛЬ
MAILEXAM_PORT=587
MAIL_FROM=noreply@example.test

SMTP-хост: ВАШ_ЛОГИН.mailexam.ru.

Адрес отправителя

MAIL_FROM может быть любым тестовым адресом — письмо попадет в Mailexam®, а не реальному получателю.

Альтернативные порты

MAILEXAM_PORT=587

Отправка через client.StartTLS (см. mail.go).

MAILEXAM_PORT=2525

Тот же путь, что для 587.

MAILEXAM_PORT=25

Используется smtp.SendMail без STARTTLS.

3. Модуль отправки почты

Тот же подход, что в Gin: mail.go в корне проекта.

// mail.go
package main

import (
    "crypto/tls"
    "fmt"
    "net"
    "net/smtp"
    "os"
    "strconv"
)

func sendTest(to, subject, body string) error {
    login := os.Getenv("MAILEXAM_LOGIN")
    password := os.Getenv("MAILEXAM_PASSWORD")
    if login == "" || password == "" {
        return fmt.Errorf("MAILEXAM_LOGIN and MAILEXAM_PASSWORD must be set")
    }

    port, _ := strconv.Atoi(os.Getenv("MAILEXAM_PORT"))
    if port == 0 {
        port = 587
    }

    from := os.Getenv("MAIL_FROM")
    if from == "" {
        from = "noreply@example.test"
    }
    if to == "" {
        to = "user@example.test"
    }
    if subject == "" {
        subject = "Echo + Mailexam"
    }
    if body == "" {
        body = "Тест Mailexam из Echo"
    }

    host := login + ".mailexam.ru"
    addr := fmt.Sprintf("%s:%d", host, port)
    auth := smtp.PlainAuth("", login, password, host)

    msg := []byte(fmt.Sprintf(
        "From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s",
        from, to, subject, body,
    ))

    if port == 587 || port == 2525 {
        return sendWithSTARTTLS(addr, host, auth, from, []string{to}, msg)
    }

    return smtp.SendMail(addr, auth, from, []string{to}, msg)
}

func sendWithSTARTTLS(addr, host string, auth smtp.Auth, from string, to []string, msg []byte) error {
    conn, err := net.Dial("tcp", addr)
    if err != nil {
        return err
    }
    defer conn.Close()

    client, err := smtp.NewClient(conn, host)
    if err != nil {
        return err
    }
    defer client.Close()

    if ok, _ := client.Extension("STARTTLS"); ok {
        if err = client.StartTLS(&tls.Config{ServerName: host}); err != nil {
            return err
        }
    }

    if auth != nil {
        if err = client.Auth(auth); err != nil {
            return err
        }
    }

    if err = client.Mail(from); err != nil {
        return err
    }
    for _, rcpt := range to {
        if err = client.Rcpt(rcpt); err != nil {
            return err
        }
    }

    w, err := client.Data()
    if err != nil {
        return err
    }
    if _, err = w.Write(msg); err != nil {
        return err
    }
    if err = w.Close(); err != nil {
        return err
    }

    return client.Quit()
}

4. Маршрут Echo

// main.go
package main

import (
    "net/http"
    "os"

    "github.com/joho/godotenv"
    "github.com/labstack/echo/v4"
)

type sendRequest struct {
    To      string `json:"to"`
    Subject string `json:"subject"`
    Body    string `json:"body"`
}

func listenAddr() string {
    host := os.Getenv("HTTP_HOST")
    if host == "" {
        host = "127.0.0.1"
    }

    port := os.Getenv("HTTP_PORT")
    if port == "" {
        port = "8080"
    }

    return host + ":" + port
}

func main() {
    _ = godotenv.Load()

    e := echo.New()

    e.POST("/mail/test", func(c echo.Context) error {
        var req sendRequest
        _ = c.Bind(&req)

        if err := sendTest(req.To, req.Subject, req.Body); err != nil {
            return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
        }

        return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
    })

    if err := e.Start(listenAddr()); err != nil {
        panic(err)
    }
}

Запуск и проверка:

go run .
curl -X POST http://127.0.0.1:8080/mail/test \
  -H 'Content-Type: application/json' \
  -d '{"to":"user@example.test","subject":"Проверка","body":"Привет"}'

Письмо появится в личном кабинете Mailexam® → ваш проект → входящие.

5. Локальная разработка и CI

Среда Рекомендация
local .env + godotenv.Load()
CI секреты MAILEXAM_LOGIN, MAILEXAM_PASSWORD в GitLab CI/CD Variables

Пример для .gitlab-ci.yml:

variables:
  MAILEXAM_LOGIN: $MAILEXAM_LOGIN
  MAILEXAM_PASSWORD: $MAILEXAM_PASSWORD
  MAILEXAM_PORT: "587"
  MAIL_FROM: "noreply@example.test"

После интеграционного теста проверьте доставку через API Mailexam®.

6. Типичные проблемы

Ошибка TLS или authentication failed

  • host в коде — {логин}.mailexam.ru, в PlainAuth — тот же логин.
  • Логин и пароль — пара из письма одного проекта.

Порт 587

  • Нужен STARTTLS (sendWithSTARTTLS), а не прямой smtp.SendMail без TLS.

Письмо не в личном кабинете

  • Смотрите входящие того же проекта Mailexam®.
  • Включите debug-режим и проверьте текст ошибки в ответе 500.

См. также