mirror of
https://github.com/Dvorinka/SendMail.git
synced 2026-07-29 01:53:47 +00:00
360 lines
12 KiB
Go
360 lines
12 KiB
Go
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"html"
|
||
"log"
|
||
"net/http"
|
||
"strings"
|
||
"strconv"
|
||
|
||
"gopkg.in/gomail.v2"
|
||
)
|
||
|
||
// Copyable snippets used by /docs and dedicated snippet endpoints
|
||
const docsHTMLSnippet = `
|
||
<div class="bg-white rounded-xl shadow-lg overflow-hidden">
|
||
<div class="md:flex">
|
||
<!-- Contact Form -->
|
||
<div class="w-full md:w-1/2 p-8">
|
||
<form id="contact-form" class="space-y-6">
|
||
<input type="hidden" name="domain" value="sportcreative.eu" />
|
||
|
||
<div>
|
||
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">Jméno a příjmení *</label>
|
||
<input type="text" id="name" name="name" required
|
||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-sport-purple focus:border-transparent transition duration-200"
|
||
placeholder="Vaše celé jméno">
|
||
</div>
|
||
|
||
<div>
|
||
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">E-mail *</label>
|
||
<input type="email" id="email" name="email" required
|
||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-sport-purple focus:border-transparent transition duration-200"
|
||
placeholder="[email protected]">
|
||
</div>
|
||
|
||
<div>
|
||
<label for="subject" class="block text-sm font-medium text-gray-700 mb-1">Předmět *</label>
|
||
<input type="text" id="subject" name="subject" required
|
||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-sport-purple focus:border-transparent transition duration-200"
|
||
placeholder="O co se zajímáte?">
|
||
</div>
|
||
|
||
<div>
|
||
<label for="message" class="block text-sm font-medium text-gray-700 mb-1">Zpráva *</label>
|
||
<textarea id="message" name="message" rows="5" required
|
||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-sport-purple focus:border-transparent transition duration-200"
|
||
placeholder="Jak vám můžeme pomoci?"></textarea>
|
||
</div>
|
||
|
||
<div class="pt-2">
|
||
<button type="submit"
|
||
class="w-full bg-sport-purple text-white font-medium py-3 px-6 rounded-full hover:shadow-lg transition duration-300 hover:bg-sport-orange">
|
||
Odeslat zprávu
|
||
<ion-icon name="arrow-forward-outline" class="ml-2"></ion-icon>
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>`
|
||
|
||
const docsJSSnippet = `
|
||
// Contact form submission
|
||
const form = document.getElementById('contact-form');
|
||
if (form) {
|
||
form.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
|
||
const submitButton = form.querySelector('button[type="submit"]');
|
||
const originalButtonText = submitButton.innerHTML;
|
||
|
||
try {
|
||
// Show loading state
|
||
submitButton.disabled = true;
|
||
submitButton.innerHTML = 'Odesílám...';
|
||
|
||
const data = Object.fromEntries(new FormData(form));
|
||
|
||
const res = await fetch('https://sendmail.tdvorak.dev/send' + window.location.search, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
const text = await res.text();
|
||
|
||
if (res.ok) {
|
||
// Show success message
|
||
alert('Děkujeme za vaši zprávu! Brzy se vám ozveme zpět.');
|
||
form.reset();
|
||
} else {
|
||
throw new Error(text || 'Něco se pokazilo. Zkuste to prosím znovu.');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error:', error);
|
||
alert(error.message || 'Při odesílání zprávy došlo k chybě. Zkuste to prosím znovu později.');
|
||
} finally {
|
||
// Reset button state
|
||
submitButton.disabled = false;
|
||
submitButton.innerHTML = originalButtonText;
|
||
}
|
||
});
|
||
}`
|
||
|
||
type FormData struct {
|
||
Domain string `json:"domain"`
|
||
Name string `json:"name"`
|
||
Email string `json:"email"`
|
||
Subject string `json:"subject"`
|
||
Message string `json:"message"`
|
||
}
|
||
|
||
// rootHandler serves a minimal HTML page with a contact form and JS to submit to /send
|
||
func rootHandler(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodGet {
|
||
http.Error(w, "GET only", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
htmlEsc := html.EscapeString(docsHTMLSnippet)
|
||
jsEsc := html.EscapeString(docsJSSnippet)
|
||
page := fmt.Sprintf(`<!DOCTYPE html>
|
||
<html lang="cs">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>SendMail API – Dokumentace</title>
|
||
<style>
|
||
body { font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; background: #0b1020; color: #e2e8f0; margin:0; }
|
||
.container { max-width: 980px; margin: 0 auto; padding: 2rem; }
|
||
h1 { margin: 0 0 .5rem; font-size: 2rem; }
|
||
.muted { color:#94a3b8; }
|
||
.card { background: #0f172a; border: 1px solid rgba(148,163,184,.18); border-radius: 12px; padding: 1rem 1.25rem; margin-bottom: 1rem; }
|
||
code, pre { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
||
pre { background: #0b1220; border:1px solid rgba(148,163,184,.18); border-radius:10px; padding:.75rem; overflow:auto; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>SendMail API</h1>
|
||
<p class="muted">Server: <code>http://localhost:8080</code></p>
|
||
|
||
<div class="card">
|
||
<h2>1) POST /send</h2>
|
||
<p>Tělo JSON: <code>{ domain?, name, email, subject, message }</code></p>
|
||
<p>Volitelné SMTP v query: <code>?email=&pass=&server=&port=</code></p>
|
||
<h3>Příklad cURL (přednastavená doména)</h3>
|
||
<pre><code>curl -X POST http://localhost:8080/send \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"domain":"sportcreative.eu",
|
||
"name":"Jan Novák",
|
||
"email":"[email protected]",
|
||
"subject":"Dotaz",
|
||
"message":"Dobrý den..."
|
||
}'
|
||
</code></pre>
|
||
<h3>Příklad cURL (SMTP v URL)</h3>
|
||
<pre><code>curl -X POST "http://localhost:8080/[email protected]&pass=SECRET&server=smtp.example.com&port=465" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"name":"Jan Novák",
|
||
"email":"[email protected]",
|
||
"subject":"Dotaz",
|
||
"message":"Dobrý den..."
|
||
}'
|
||
</code></pre>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>2) GET /send-params</h2>
|
||
<p>Query: <code>?email=&pass=&server=&port=&to=&from=&subject=&message=</code> (když chybí <code>to</code>/<code>from</code>, použije se <code>email</code>).</p>
|
||
<h3>Příklad cURL</h3>
|
||
<pre><code>curl "http://localhost:8080/[email protected]&pass=SECRET&server=smtp.example.com&port=465&[email protected]&[email protected]&subject=Hello&message=Ahoj"</code></pre>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>HTML formulář (vložit do stránky)</h2>
|
||
<pre><code>%s</code></pre>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>JavaScript (vložit do stránky)</h2>
|
||
<pre><code>%s</code></pre>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>`, htmlEsc, jsEsc)
|
||
w.Write([]byte(page))
|
||
}
|
||
|
||
type EmailConfig struct {
|
||
Email string
|
||
Password string
|
||
SMTPHost string
|
||
SMTPPort int
|
||
}
|
||
|
||
var domainMap = map[string]EmailConfig{
|
||
"tdvorak.dev": {"[email protected]", "%8s3Yad*!b3*t", "smtp.purelymail.com", 465},
|
||
"vbly.org": {"[email protected]", "!N0^e0Q9k*mV", "smtp.purelymail.com", 465},
|
||
"sportcreative.eu": {"[email protected]", "@dN$TZPgG5!", "smtp.purelymail.com", 465},
|
||
"reklik.net": {"[email protected]", "FK8eAwJbSkP2%H", "smtp.purelymail.com", 465},
|
||
"zeusport.eu": {"[email protected]", "r-:#sSQ?9D%C4t8", "smtp.forpsi.com", 465},
|
||
}
|
||
|
||
func sendHandler(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodPost {
|
||
http.Error(w, "POST only", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
var data FormData
|
||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// Determine SMTP configuration: prefer query params if provided; otherwise use domain map
|
||
q := r.URL.Query()
|
||
qEmail := strings.TrimSpace(q.Get("email"))
|
||
qPass := q.Get("pass")
|
||
qServer := strings.TrimSpace(q.Get("server"))
|
||
qPortStr := strings.TrimSpace(q.Get("port"))
|
||
|
||
var config EmailConfig
|
||
if qEmail != "" || qPass != "" || qServer != "" || qPortStr != "" {
|
||
if qEmail == "" || qPass == "" || qServer == "" || qPortStr == "" {
|
||
http.Error(w, "Missing required query params: email, pass, server, port", http.StatusBadRequest)
|
||
return
|
||
}
|
||
port, err := strconv.Atoi(qPortStr)
|
||
if err != nil || port <= 0 {
|
||
http.Error(w, "Invalid port", http.StatusBadRequest)
|
||
return
|
||
}
|
||
config = EmailConfig{Email: qEmail, Password: qPass, SMTPHost: qServer, SMTPPort: port}
|
||
} else {
|
||
data.Domain = strings.ToLower(strings.TrimSpace(data.Domain))
|
||
var ok bool
|
||
config, ok = domainMap[data.Domain]
|
||
if !ok {
|
||
http.Error(w, "Unknown domain", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
}
|
||
|
||
m := gomail.NewMessage()
|
||
m.SetHeader("From", config.Email)
|
||
m.SetHeader("To", config.Email) // you can change this to forward to another inbox
|
||
if strings.TrimSpace(data.Email) != "" {
|
||
m.SetHeader("Reply-To", data.Email)
|
||
}
|
||
m.SetHeader("Subject", data.Subject)
|
||
m.SetBody("text/plain", "Name: "+data.Name+"\nEmail: "+data.Email+"\n\n"+data.Message)
|
||
|
||
d := gomail.NewDialer(config.SMTPHost, config.SMTPPort, config.Email, config.Password)
|
||
d.SSL = true // Using SSL for secure connection
|
||
|
||
if err := d.DialAndSend(m); err != nil {
|
||
log.Println("Email send error:", err)
|
||
http.Error(w, "Failed to send email", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
w.Write([]byte("Email sent successfully"))
|
||
}
|
||
|
||
func enableCors(handler http.HandlerFunc) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
// Allow all origins
|
||
origin := r.Header.Get("Origin")
|
||
if origin != "" {
|
||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
|
||
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization")
|
||
}
|
||
|
||
// Handle preflight requests
|
||
if r.Method == http.MethodOptions {
|
||
w.WriteHeader(http.StatusOK)
|
||
return
|
||
}
|
||
|
||
handler(w, r)
|
||
}
|
||
}
|
||
|
||
// sendParamsHandler handles sending email with explicit SMTP parameters via query string.
|
||
// Example:
|
||
// GET /[email protected]&pass=secret&server=smtp.example.com&port=465&[email protected]&[email protected]&subject=Hello&message=Hi
|
||
func sendParamsHandler(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodGet {
|
||
http.Error(w, "GET only", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
q := r.URL.Query()
|
||
smtpEmail := strings.TrimSpace(q.Get("email"))
|
||
smtpPass := q.Get("pass")
|
||
smtpServer := strings.TrimSpace(q.Get("server"))
|
||
portStr := strings.TrimSpace(q.Get("port"))
|
||
to := strings.TrimSpace(q.Get("to"))
|
||
from := strings.TrimSpace(q.Get("from"))
|
||
subject := q.Get("subject")
|
||
message := q.Get("message")
|
||
|
||
if smtpEmail == "" || smtpPass == "" || smtpServer == "" || portStr == "" {
|
||
http.Error(w, "Missing required params: email, pass, server, port", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
port, err := strconv.Atoi(portStr)
|
||
if err != nil || port <= 0 {
|
||
http.Error(w, "Invalid port", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
if from == "" {
|
||
from = smtpEmail
|
||
}
|
||
if to == "" {
|
||
to = smtpEmail
|
||
}
|
||
|
||
m := gomail.NewMessage()
|
||
m.SetHeader("From", from)
|
||
m.SetHeader("To", to)
|
||
if subject == "" {
|
||
subject = "(no subject)"
|
||
}
|
||
m.SetHeader("Subject", subject)
|
||
m.SetBody("text/plain", message)
|
||
|
||
d := gomail.NewDialer(smtpServer, port, smtpEmail, smtpPass)
|
||
d.SSL = true
|
||
|
||
if err := d.DialAndSend(m); err != nil {
|
||
log.Println("Email send error:", err)
|
||
http.Error(w, "Failed to send email", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
w.Header().Set("Content-Type", "application/json")
|
||
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok", "message": "Email sent successfully"})
|
||
}
|
||
|
||
// docsHandler serves a minimal JSON API description.
|
||
|
||
func main() {
|
||
http.HandleFunc("/send", enableCors(sendHandler))
|
||
http.HandleFunc("/send-params", enableCors(sendParamsHandler))
|
||
http.HandleFunc("/", enableCors(rootHandler))
|
||
log.Println("Server running on :8080")
|
||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||
}
|
||
|