This commit is contained in:
Tomáš Dvořák
2025-09-10 14:08:19 +02:00
parent d9e2b521d0
commit a37eb3f859
+272 -9
View File
@@ -2,13 +2,108 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt"
"html"
"log" "log"
"net/http" "net/http"
"strings" "strings"
"strconv"
"gopkg.in/gomail.v2" "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 { type FormData struct {
Domain string `json:"domain"` Domain string `json:"domain"`
Name string `json:"name"` Name string `json:"name"`
@@ -17,6 +112,85 @@ type FormData struct {
Message string `json:"message"` 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 { type EmailConfig struct {
Email string Email string
Password string Password string
@@ -44,16 +218,41 @@ func sendHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
data.Domain = strings.ToLower(strings.TrimSpace(data.Domain)) // Determine SMTP configuration: prefer query params if provided; otherwise use domain map
config, ok := domainMap[data.Domain] q := r.URL.Query()
if !ok { qEmail := strings.TrimSpace(q.Get("email"))
http.Error(w, "Unknown domain", http.StatusUnauthorized) qPass := q.Get("pass")
return 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 := gomail.NewMessage()
m.SetHeader("From", config.Email) m.SetHeader("From", config.Email)
m.SetHeader("To", config.Email) // you can change this to forward to another inbox 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.SetHeader("Subject", data.Subject)
m.SetBody("text/plain", "Name: "+data.Name+"\nEmail: "+data.Email+"\n\n"+data.Message) m.SetBody("text/plain", "Name: "+data.Name+"\nEmail: "+data.Email+"\n\n"+data.Message)
@@ -89,8 +288,72 @@ func enableCors(handler http.HandlerFunc) http.HandlerFunc {
} }
} }
func main() { // sendParamsHandler handles sending email with explicit SMTP parameters via query string.
http.HandleFunc("/send", enableCors(sendHandler)) // Example:
log.Println("Server running on :8080") // GET /[email protected]&pass=secret&server=smtp.example.com&port=465&[email protected]&[email protected]&subject=Hello&message=Hi
log.Fatal(http.ListenAndServe(":8080", nil)) 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))
}