一个go 的skill 案例

TwoAdmin 2025-10-28 2 10/28

目录

skills/
├--- send-email/
├------ SKILL.md
├------ references/
└-----  scripts/

SKILL.md

---
name: send-email-go
description: Send emails via pre-built `send-email` binary. JSON config + env overrides for SMTP settings.
category: email
---

# Send Email via Go Binary

## When to use
User asks to send an email programmatically. A pre-built binary is available — no `go run` needed.

## Binary location
```
~/.hermes/skills/email/send-email-go/scripts/send-email
```

## Usage

### 1. Create a JSON config file
```json
{
  "host": "smtp.gmail.com",
  "port": "587",
  "from": "your-email@gmail.com",
  "to": ["recipient@example.com"],
  "subject": "Test Email",
  "body": "Hello from Hermes!",
  "is_html": false
}
```

### 2. Run
```bash
# Direct call (credentials in JSON)
~/.hermes/skills/email/send-email-go/scripts/send-email config.json

# Env override (recommended for production)
SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_USER=me@gmail.com SMTP_PASS=app-pass \
~/.hermes/skills/email/send-email-go/scripts/send-email config.json
```

Env variables override JSON values. Available: `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM`.

### 3. No-arg usage
Running without args prints usage help:
```bash
~/.hermes/skills/email/send-email-go/scripts/send-email
```

## Config fields
| Field     | Required | Description                        |
|-----------|----------|------------------------------------|
| host      | yes      | SMTP server hostname               |
| port      | yes      | Port (587 for STARTTLS, 465 for SSL)|
| username  | yes      | SMTP login user                    |
| password  | yes      | SMTP password / app password       |
| from      | yes      | Sender email                       |
| to        | yes      | Array of recipient emails          |
| subject   | yes      | Email subject line                 |
| body      | yes      | Email body content                 |
| is_html   | no       | Set true for HTML emails (default: false) |

## Pitfalls
- Gmail and many providers block regular passwords; use App Passwords or OAuth2
- Port 587 uses STARTTLS; port 465 uses implicit TLS (binary handles both automatically)
- Some SMTP servers require `From` to match the authenticated user
- Corporate firewalls may block outbound SMTP ports
- Always pass credentials via env vars, not hardcoded in config files

## Verification
Check the recipient's inbox (and spam folder) to confirm delivery.

## Rebuilding
If source changes, rebuild from the skill directory:
```bash
cd ~/.hermes/skills/email/send-email-go/scripts
go build -o send-email send_email.go
```

scripts 目录

send_email.go

package main

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net/smtp"
	"os"
	"strings"
)

type EmailConfig struct {
	Host     string   `json:"host"`
	Port     string   `json:"port"`
	Username string   `json:"username"`
	Password string   `json:"password"`
	From     string   `json:"from"`
	To       []string `json:"to"`
	Subject  string   `json:"subject"`
	Body     string   `json:"body"`
	IsHTML   bool     `json:"is_html"`
}

func buildMessage(cfg EmailConfig) string {
	header := make(map[string]string)
	header["From"] = cfg.From
	header["To"] = strings.Join(cfg.To, ",")
	header["Subject"] = cfg.Subject

	contentType := "text/plain; charset=UTF-8"
	if cfg.IsHTML {
		contentType = "text/html; charset=UTF-8"
	}

	msg := ""
	for k, v := range header {
		msg += fmt.Sprintf("%s: %s\r\n", k, v)
	}
	msg += fmt.Sprintf("MIME-version: 1.0;\r\nContent-Type: %s\r\n\r\n%s", contentType, cfg.Body)
	return msg
}

func sendEmail(cfg EmailConfig) error {
	addr := cfg.Host + ":" + cfg.Port
	auth := smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)
	msg := buildMessage(cfg)

	// Try TLS on port 465, STARTTLS on 587
	if cfg.Port == "465" {
		tlsConfig := &tls.Config{ServerName: cfg.Host}
		conn, err := tls.Dial("tcp", addr, tlsConfig)
		if err != nil {
			return fmt.Errorf("tls dial: %w", err)
		}
		defer conn.Close()
		client, err := smtp.NewClient(conn, cfg.Host)
		if err != nil {
			return fmt.Errorf("smtp client: %w", err)
		}
		defer client.Close()
		if err = client.Auth(auth); err != nil {
			return fmt.Errorf("auth: %w", err)
		}
		if err = client.Mail(cfg.From); err != nil {
			return fmt.Errorf("mail from: %w", err)
		}
		for _, to := range cfg.To {
			if err = client.Rcpt(to); err != nil {
				return fmt.Errorf("rcpt to: %w", err)
			}
		}
		w, err := client.Data()
		if err != nil {
			return fmt.Errorf("data: %w", err)
		}
		_, err = w.Write([]byte(msg))
		if err != nil {
			return fmt.Errorf("write: %w", err)
		}
		return w.Close()
	}
	return smtp.SendMail(addr, auth, cfg.From, cfg.To, []byte(msg))
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: send-email <config.json>")
		fmt.Println("  config.json: {host, port, username, password, from, to[], subject, body, is_html?}")
		os.Exit(1)
	}

	data, err := os.ReadFile(os.Args[1])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error reading config: %v\n", err)
		os.Exit(1)
	}

	var cfg EmailConfig
	if err := json.Unmarshal(data, &cfg); err != nil {
		fmt.Fprintf(os.Stderr, "Error parsing config: %v\n", err)
		os.Exit(1)
	}

	// Allow env overrides
	if h := os.Getenv("SMTP_HOST"); h != "" {
		cfg.Host = h
	}
	if p := os.Getenv("SMTP_PORT"); p != "" {
		cfg.Port = p
	}
	if u := os.Getenv("SMTP_USER"); u != "" {
		cfg.Username = u
	}
	if pw := os.Getenv("SMTP_PASS"); pw != "" {
		cfg.Password = pw
	}
	if f := os.Getenv("SMTP_FROM"); f != "" {
		cfg.From = f
	}

	if err := sendEmail(cfg); err != nil {
		fmt.Fprintf(os.Stderr, "Failed to send email: %v\n", err)
		os.Exit(1)
	}
	fmt.Println("Email sent successfully!")
}

go.mod

module send-email

go 1.21

 

- THE END -

TwoAdmin

5月26日15:11

最后修改:2026年5月26日
0

非特殊说明,本博所有文章均为博主原创。