email kinda working
This commit is contained in:
119
internal/backend/email.go
Normal file
119
internal/backend/email.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/smtp"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-ical"
|
||||
)
|
||||
|
||||
// sendInvitation sends an iMIP (RFC 6047) invitation email.
|
||||
// It includes a plain-text fallback and a METHOD:REQUEST iCalendar attachment
|
||||
// that calendar clients (Thunderbird, Apple, etc.) will recognize.
|
||||
func (b *DBBackend) sendInvitation(senderName, recipientEmail, summary, description, start, end, objectPath, originalICS string) error {
|
||||
fromAddr := fmt.Sprintf("%s@%s", senderName, b.emailDomain)
|
||||
if b.smtp.User != "" {
|
||||
fromAddr = b.smtp.User
|
||||
}
|
||||
fromHeader := fmt.Sprintf("%s <%s>", senderName, fromAddr)
|
||||
|
||||
baseURL := strings.TrimSuffix(b.publicURL, "/")
|
||||
acceptURL := fmt.Sprintf("%s/respond?path=%s&attendee=%s&status=ACCEPTED", baseURL, url.QueryEscape(objectPath), url.QueryEscape(recipientEmail))
|
||||
declineURL := fmt.Sprintf("%s/respond?path=%s&attendee=%s&status=DECLINED", baseURL, url.QueryEscape(objectPath), url.QueryEscape(recipientEmail))
|
||||
|
||||
// 1. Prepare plain-text fallback - with prominent links
|
||||
textPart := "PLEASE RESPOND TO THIS INVITATION:\r\n"
|
||||
textPart += fmt.Sprintf("✅ ACCEPT: %s\r\n", acceptURL)
|
||||
textPart += fmt.Sprintf("❌ DECLINE: %s\r\n", declineURL)
|
||||
textPart += "\r\n------------------------------------------\r\n\r\n"
|
||||
textPart += fmt.Sprintf("You have been invited to an event by %s.\r\n\r\n", senderName)
|
||||
textPart += fmt.Sprintf("Event: %s\r\n", summary)
|
||||
|
||||
// 2. Prepare iCalendar part with METHOD:REQUEST
|
||||
var icsContent string
|
||||
calendar, err := ical.NewDecoder(strings.NewReader(originalICS)).Decode()
|
||||
if err == nil {
|
||||
calendar.Props.SetText("METHOD", "REQUEST")
|
||||
// Discourage clients from sending their own response emails
|
||||
for _, event := range calendar.Events() {
|
||||
for _, attendee := range event.Props["ATTENDEE"] {
|
||||
attendee.Params.Set("RSVP", "FALSE")
|
||||
}
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := ical.NewEncoder(&buf).Encode(calendar); err == nil {
|
||||
icsContent = buf.String()
|
||||
}
|
||||
}
|
||||
if icsContent == "" {
|
||||
icsContent = originalICS // Fallback to raw if decoding failed
|
||||
}
|
||||
|
||||
// 3. Construct Multipart MIME Email
|
||||
boundary := "nxcaldav_invite_boundary"
|
||||
subject := fmt.Sprintf("Invitation: %s", summary)
|
||||
|
||||
header := fmt.Sprintf("Subject: %s\r\n", subject)
|
||||
header += fmt.Sprintf("From: %s\r\n", fromHeader)
|
||||
header += fmt.Sprintf("To: %s\r\n", recipientEmail)
|
||||
header += "MIME-Version: 1.0\r\n"
|
||||
header += fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\r\n", boundary)
|
||||
header += "\r\n"
|
||||
|
||||
body := fmt.Sprintf("--%s\r\n", boundary)
|
||||
body += "Content-Type: text/plain; charset=UTF-8\r\n"
|
||||
body += "Content-Transfer-Encoding: 7bit\r\n"
|
||||
body += "\r\n"
|
||||
body += textPart + "\r\n"
|
||||
|
||||
body += fmt.Sprintf("--%s\r\n", boundary)
|
||||
body += "Content-Type: text/calendar; method=REQUEST; charset=UTF-8\r\n"
|
||||
body += "Content-Transfer-Encoding: 7bit\r\n"
|
||||
body += "\r\n"
|
||||
body += icsContent + "\r\n"
|
||||
body += fmt.Sprintf("--%s--\r\n", boundary)
|
||||
|
||||
// 4. Send the mail
|
||||
addr := fmt.Sprintf("%s:%d", b.smtp.Host, b.smtp.Port)
|
||||
tlsConfig := &tls.Config{InsecureSkipVerify: true, ServerName: b.smtp.Host}
|
||||
|
||||
var c *smtp.Client
|
||||
if b.smtp.Port == 465 {
|
||||
conn, err := tls.Dial("tcp", addr, tlsConfig)
|
||||
if err != nil { return err }
|
||||
c, err = smtp.NewClient(conn, b.smtp.Host)
|
||||
} else {
|
||||
c, err = smtp.Dial(addr)
|
||||
}
|
||||
if err != nil { return err }
|
||||
defer c.Close()
|
||||
|
||||
if err = c.Hello("localhost"); err != nil { return err }
|
||||
if b.smtp.Port != 465 {
|
||||
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||
if err = c.StartTLS(tlsConfig); err != nil { return err }
|
||||
}
|
||||
}
|
||||
if b.smtp.User != "" && b.smtp.Password != "" {
|
||||
auth := smtp.PlainAuth("", b.smtp.User, b.smtp.Password, b.smtp.Host)
|
||||
if err = c.Auth(auth); err != nil { return err }
|
||||
}
|
||||
|
||||
if err = c.Mail(fromAddr); err != nil { return err }
|
||||
if err = c.Rcpt(recipientEmail); err != nil { return err }
|
||||
|
||||
w, err := c.Data()
|
||||
if err != nil { return err }
|
||||
_, err = w.Write([]byte(header + body))
|
||||
if err != nil { return err }
|
||||
err = w.Close()
|
||||
if err != nil { return err }
|
||||
|
||||
log.Printf("[email] Successfully sent iMIP invitation to %s", recipientEmail)
|
||||
return c.Quit()
|
||||
}
|
||||
Reference in New Issue
Block a user