Skip to content

Beego

Guide for applications built with Beego 2.x (Go 1.22+). Mailexam connects as SMTP through the standard library net/smtp with STARTTLS on port 587.

What you need

  • A Mailexam account and a project with SMTP credentials.
  • Go 1.22+.

Copy from the welcome email (or dashboard) for your project:

  • YOUR_LOGIN — SMTP login (for example, xxxxx);
  • YOUR_PASSWORD — SMTP password (a unique pair with the login);
  • host — YOUR_LOGIN.mailexam.io (matches the login).

1. Dependencies

go mod init example.com/mailexam-beego
go get github.com/beego/beego/v2@v2.3.4
go get github.com/joho/godotenv@v1.5.1

go.mod file (versions are resolved after go get):

module example.com/mailexam-beego

go 1.22

require (
    github.com/beego/beego/v2 v2.3.4
    github.com/joho/godotenv v1.5.1
)

2. Environment variables

.env file in the project root (do not commit passwords to git):

MAILEXAM_LOGIN=YOUR_LOGIN
MAILEXAM_PASSWORD=YOUR_PASSWORD
MAILEXAM_PORT=587
MAIL_FROM=noreply@example.test

SMTP host: YOUR_LOGIN.mailexam.io.

Sender address

MAIL_FROM can be any test address — the message goes to Mailexam, not to a real recipient.

Alternative ports

MAILEXAM_PORT=587

Sending via client.StartTLS (see mail.go).

MAILEXAM_PORT=2525

Same path as for 587.

MAILEXAM_PORT=25

Uses smtp.SendMail without STARTTLS.

3. Mail sending module

Same approach as in Gin: mail.go in the project root.

// 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 = "Beego + Mailexam"
    }
    if body == "" {
        body = "Mailexam test from Beego"
    }

    host := login + ".mailexam.io"
    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. Controller and route

// main.go
package main

import (
    "encoding/json"

    "github.com/joho/godotenv"
    beego "github.com/beego/beego/v2/server/web"
)

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

type MailController struct {
    beego.Controller
}

func (c *MailController) PostTest() {
    var req sendRequest
    if len(c.Ctx.Input.RequestBody) > 0 {
        _ = json.Unmarshal(c.Ctx.Input.RequestBody, &req)
    }

    if err := sendTest(req.To, req.Subject, req.Body); err != nil {
        c.Ctx.Output.SetStatus(500)
        c.Data["json"] = map[string]string{"error": err.Error()}
        c.ServeJSON()
        return
    }

    c.Data["json"] = map[string]string{"status": "ok"}
    c.ServeJSON()
}

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

    beego.BConfig.Listen.HTTPAddr = "127.0.0.1"
    beego.BConfig.Listen.HTTPPort = 8080

    beego.Router("/mail/test", &MailController{}, "post:PostTest")

    beego.Run()
}

Start and verify:

go run .
curl -X POST http://127.0.0.1:8080/mail/test \
  -H 'Content-Type: application/json' \
  -d '{"to":"user@example.test","subject":"Test","body":"Hello"}'

The message will appear in the Mailexam dashboard → your project → inbox.

bee new structure (optional)

After bee new, move MailController to controllers/mail.go, route — to routers/router.go:

beego.Router("/mail/test", &controllers.MailController{}, "post:PostTest")

5. Local development and CI

Environment Recommendation
local .env + godotenv.Load() in main
CI secrets MAILEXAM_LOGIN, MAILEXAM_PASSWORD in GitLab CI/CD Variables

Example for .gitlab-ci.yml:

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

After an integration test, verify delivery via the Mailexam API.

6. Common issues

TLS or authentication failed error

  • host in code — {login}.mailexam.io, in PlainAuth — the same login.
  • Login and password are a pair from the email for one project.

Port 587

  • Requires STARTTLS (sendWithSTARTTLS), not direct smtp.SendMail without TLS.

Empty POST body

  • Beego reads the body in c.Ctx.Input.RequestBody; for JSON pass the Content-Type: application/json header.

Message not in the dashboard

  • View the inbox of the same Mailexam project.
  • Set beego.BConfig.RunMode = "dev" and check logs on a 500 response.

See also