mirror of
https://github.com/ivabus/pop
synced 2024-11-10 02:25:19 +03:00
feat: send email
This commit is contained in:
parent
5f884c4946
commit
daa31dc744
53
email.go
Normal file
53
email.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/resendlabs/resend-go"
|
||||
)
|
||||
|
||||
// sendEmailSuccessMsg is the tea.Msg handled by Bubble Tea when the email has
|
||||
// been sent successfully.
|
||||
type sendEmailSuccessMsg struct{}
|
||||
|
||||
// sendEmailFailureMsg is the tea.Msg handled by Bubble Tea when the email has
|
||||
// failed to send.
|
||||
type sendEmailFailureMsg error
|
||||
|
||||
// sendEmailCmd returns a tea.Cmd that sends the email.
|
||||
func (m Model) sendEmailCmd() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
attachments := make([]string, len(m.Attachments.Items()))
|
||||
for i, a := range m.Attachments.Items() {
|
||||
at, ok := a.(attachment)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
attachments[i] = string(at)
|
||||
}
|
||||
err := sendEmail(m.From.Value(), m.To.Value(), m.Subject.Value(), m.Body.Value(), attachments)
|
||||
if err != nil {
|
||||
return sendEmailFailureMsg(err)
|
||||
}
|
||||
return sendEmailSuccessMsg{}
|
||||
}
|
||||
}
|
||||
|
||||
func sendEmail(from, to, subject, body string, attachments []string) error {
|
||||
client := resend.NewClient(os.Getenv(RESEND_API_KEY))
|
||||
|
||||
request := &resend.SendEmailRequest{
|
||||
From: from,
|
||||
To: []string{to},
|
||||
Subject: subject,
|
||||
Html: body,
|
||||
}
|
||||
|
||||
_, err := client.Emails.Send(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
5
go.mod
5
go.mod
|
@ -4,8 +4,9 @@ go 1.19
|
|||
|
||||
require (
|
||||
github.com/charmbracelet/bubbles v0.16.1
|
||||
github.com/charmbracelet/bubbletea v0.24.3-0.20230609163353-b80eb8303bba
|
||||
github.com/charmbracelet/bubbletea v0.24.3-0.20230614142509-c0cc6aa1fb4f
|
||||
github.com/charmbracelet/lipgloss v0.7.1
|
||||
github.com/resendlabs/resend-go v1.6.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
)
|
||||
|
@ -28,7 +29,7 @@ require (
|
|||
github.com/sahilm/fuzzy v0.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/term v0.6.0 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
)
|
||||
|
|
13
go.sum
13
go.sum
|
@ -4,13 +4,14 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
|
|||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
||||
github.com/charmbracelet/bubbletea v0.24.3-0.20230609163353-b80eb8303bba h1:hWjlqzPiG3dWN5KZUAKKnqz/R8BlYqX1Aw+nk7riQss=
|
||||
github.com/charmbracelet/bubbletea v0.24.3-0.20230609163353-b80eb8303bba/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
||||
github.com/charmbracelet/bubbletea v0.24.3-0.20230614142509-c0cc6aa1fb4f h1:piPKA2I5VF6qDNPoDxw33/1wr7C5AZV/nAziJ5P4pM8=
|
||||
github.com/charmbracelet/bubbletea v0.24.3-0.20230614142509-c0cc6aa1fb4f/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
||||
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
|
||||
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
|
@ -33,6 +34,9 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
|||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
|
||||
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/resendlabs/resend-go v1.6.0 h1:QnXJv71HVKHS+IDv9anK5XpYCegHH5MLK1W3q4cAvjk=
|
||||
github.com/resendlabs/resend-go v1.6.0/go.mod h1:yip1STH7Bqfm4fD0So5HgyNbt5taG5Cplc4xXxETyLI=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
|
@ -43,16 +47,19 @@ github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
|||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
10
main.go
10
main.go
|
@ -1,12 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const RESEND_API_KEY = "RESEND_API_KEY"
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "email",
|
||||
Short: "email is a command line interface for sending emails.",
|
||||
|
@ -22,6 +25,13 @@ var rootCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
func main() {
|
||||
key := os.Getenv(RESEND_API_KEY)
|
||||
if key == "" {
|
||||
fmt.Printf("\n %s %s %s\n\n", errorHeaderStyle.String(), inlineCodeStyle.Render(RESEND_API_KEY), "environment variable is required.")
|
||||
fmt.Printf(" %s %s\n\n", commentStyle.Render("You can grab one at"), linkStyle.Render("https://resend.com"))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
|
|
22
model.go
22
model.go
|
@ -51,6 +51,7 @@ type Model struct {
|
|||
help help.Model
|
||||
keymap KeyMap
|
||||
quitting bool
|
||||
err error
|
||||
}
|
||||
|
||||
func NewModel() Model {
|
||||
|
@ -106,7 +107,7 @@ func NewModel() Model {
|
|||
picker := filepicker.New()
|
||||
picker.CurrentDirectory, _ = os.UserHomeDir()
|
||||
|
||||
loadingSpinner := spinner.NewModel()
|
||||
loadingSpinner := spinner.New()
|
||||
loadingSpinner.Spinner = spinner.Dot
|
||||
|
||||
return Model{
|
||||
|
@ -132,13 +133,12 @@ func (m Model) Init() tea.Cmd {
|
|||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.From.Width = msg.Width - 2
|
||||
m.To.Width = msg.Width - 2
|
||||
m.Subject.Width = msg.Width - 2
|
||||
m.Body.SetWidth(msg.Width - 2)
|
||||
m.Attachments.SetWidth(msg.Width - 2)
|
||||
|
||||
case sendEmailSuccessMsg:
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
case sendEmailFailureMsg:
|
||||
m.state = editingFrom
|
||||
m.err = msg
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, m.keymap.NextInput):
|
||||
|
@ -178,6 +178,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.state = sendingEmail
|
||||
return m, tea.Batch(
|
||||
m.loadingSpinner.Tick,
|
||||
m.sendEmailCmd(),
|
||||
)
|
||||
case key.Matches(msg, m.keymap.Attach):
|
||||
m.state = pickingFile
|
||||
|
@ -295,6 +296,11 @@ func (m Model) View() string {
|
|||
s.WriteString("\n")
|
||||
s.WriteString(m.help.View(m.keymap))
|
||||
|
||||
if m.err != nil {
|
||||
s.WriteString("\n\n")
|
||||
s.WriteString(errorStyle.Render(m.err.Error()))
|
||||
}
|
||||
|
||||
return paddedStyle.Render(s.String())
|
||||
}
|
||||
|
||||
|
|
7
style.go
7
style.go
|
@ -19,4 +19,11 @@ var (
|
|||
cursorStyle = lipgloss.NewStyle().Foreground(whiteColor)
|
||||
|
||||
paddedStyle = lipgloss.NewStyle().Padding(1)
|
||||
|
||||
errorHeaderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#F1F1F1")).Background(lipgloss.Color("#FF5F87")).Bold(true).Padding(0, 1).SetString("ERROR")
|
||||
errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF5F87"))
|
||||
commentStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#757575"))
|
||||
|
||||
inlineCodeStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF5F87")).Background(lipgloss.Color("#3A3A3A")).Padding(0, 1)
|
||||
linkStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#00AF87")).Underline(true)
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue