feat: send email

This commit is contained in:
Maas Lalani 2023-06-15 09:45:14 -04:00
parent 5f884c4946
commit daa31dc744
No known key found for this signature in database
GPG key ID: 5A6ED5CBF1A0A000
6 changed files with 97 additions and 13 deletions

53
email.go Normal file
View 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
View file

@ -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
View file

@ -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
View file

@ -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)

View file

@ -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())
}

View file

@ -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)
)