Go/Python으로 Outlook 메일 보내기

Go나 Python을 사용하여 Outlook으로 메일을 보내는 방법을 공유한다. (OLE 이용)


GoPython으로 메일을 보내는 방법은 많이 알려져있고, 실제로 나도 이 방법을 이용하여 몇 가지 업무 자동화를 하고 있다. 그런데 업무 특성상 무조건 메일을 보내면 곤란하고 보내기 전에 사전 점검이나 자동화기 안되는 수정이 필요한 경우도 있다.
이런 경우에는 Outlook 메일 화면을 발송은 하지 않은 상태로 띄운 후, 점검 및 수정을 한 후 발송하면 편한데, Go나 Python으로 구현하는 방법을 몰랐다가 이번에 OLE(Object Linking and Embedding)를 이용한 방법을 알게 되어 정리해 본다.


사실 나는 이런 용도의 것은 Outlook에서 VBA로 작성했었는데, VBA로 구현이 힘든 것은 Go나 Python으로 구현해서 호출하고 있었다. 즉, 이 방법으로도 원하는 업무 자동화는 달성했었지만 구현이 VBA와 Go/Python으로 나누어지는 불편함이 있었는데 (더구나 아무래도 VBA는 익숙하지 않으니), Go나 Python으로 구현하면 VBA를 사용할 필요가 없으므로 좀 더 편리한 환경으로 구현할 수 있겠다.😋)

Python으로 메일 보내기

Gmail SMTP를 이용한 예제이다. 먼저 Gmail에서 아래와 같은 사전 준비가 필요하다.

  1. Gmail 설정에서 “전달 및 POP/IMAP” -> “IMAP 사용”을 체크한다.
  2. https://myaccount.google.com/security 페이지에서 “2단계 인증” 항목을 클릭하여 안내대로 진행한다.
  3. 2단계 인증이 완료되면 다시 https://myaccount.google.com/security 페이지에서 “앱 비밀번호” 항목을 클릭하여 본인이 사용하는 프로그램에 대한 앱과 기기를 선택한 후 (앱은 “메일”로 선택), “생성” 버튼을 누르면, 비밀번호가 생성된다.
  4. 위에서 얻은 비밀번호를 아래 Python 예제 코드에서 SMTP login 시 Gmail ID/PW에 사용하면 된다.

아래 예제 코드를 참조한다.

import smtplib
from email.mime.text import MIMEText

smtp = smtplib.SMTP('smtp.gmail.com', 587)
smtp.starttls()
smtp.login('Gmail ID', 'Gmail PW')

mailBodyMsg = "<font style=\"font-family: '맑은 고딕'; font-size: 10pt; \"><font color='#1f4e79'>메일 본문</font>"
msg = MIMEText(mailBodyMsg, 'html')
msg['Subject'] = '메일 제목'
smtp.sendmail("", "수신인메일주소", msg.as_string())
smtp.quit()

Go로 메일 보내기

예제 1

아래 예제는 Outlook을 이용하여 메일을 보낸다. Outlook SMTP는 LOGIN 방식인데, SMTP 패키지는 PLAIN auth만 지원하므로, smtp.Auth 인터페이스를 이용하여 login을 하였다.

package main

import (
    "errors"
    "log"
    "net/smtp"
    "strconv"
)

func main() {
    server := "smtp-mail.outlook.com"
    port := 25
    user := "본인메일주소"
    pw := "본인메일암호"
    dest := "수신인메일주소"
    cc := "참조인메일주소"
    endpoint := server + ":" + strconv.Itoa(port)
    auth := myLoginAuth(user, pw)
    from := user
    to := []string{dest}
    subjectMsg := "메일 제목"
    bodyMsg := "메일 본문"
    headerBlank := "\r\n"
    FromAddr := "From: " + from + headerBlank
    toAddr := "To: " + dest + headerBlank
    ccAddr := "Cc: " + cc + headerBlank
    headerSubject := "Subject: " + subjectMsg + headerBlank
    body := bodyMsg + headerBlank
    msg := []byte(FromAddr + toAddr + headerSubject + ccAddr + headerBlank + body)
    err := smtp.SendMail(endpoint, auth, from, to, msg)
    if err != nil {
        log.Fatal(err)
    }
}
type loginAuth struct {
    username, password string
}
func myLoginAuth(username, password string) smtp.Auth {
    return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
    return "LOGIN", []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
    if more {
        switch string(fromServer) {
        case "Username:":
            return []byte(a.username), nil
        case "Password:":
            return []byte(a.password), nil
        default:
            return nil, errors.New("Unkown from server")
        }
    }
    return nil, nil
}

예제 2

아래 예제는 “gopkg.in/mail.v2” 패키지를 이용한 방식이다.

package main

import (
    "fmt"

    "gopkg.in/mail.v2"
)

func main {
    user := "본인메일주소"
    pw := "본인메일암호"
    receiver := "수신인메일주소"
    cc := "참조인메일주소"
    subject := "메일 제목"
    fileName := "첨부 파일 이름"
    d := mail.NewDialer("smtp-mail.outlook.com", 25, user, pw)

    msg := mail.NewMessage()
    msg.SetHeader("From", user)
    msg.SetHeader("To", receiver)
    msg.SetHeader("Cc", cc)
    msg.SetHeader("Subject", subject)
    msg.SetBody("text/html", "<b>Hello</b> <i>world!</i>!")
    msg.Attach(fileName)

    if err := d.DialAndSend(msg); err != nil {
        fmt.Println("Fail to send an email.", err)
        return err
    }

    return nil
}

Python으로 Outlook 메일 팝업 띄우기

아래 예제는 Python으로 OLE를 이용하여 Outlook 메일 팝업을 띄우는 예제이다. (Outlook이 실행 중이 아니어도 됨)

from win32com.client import Dispatch
import win32com.client as win32

outlook = win32.Dispatch('outlook.application')
mail = outlook.CreateItem(0)

mail.To = "수신인메일주소"
mail.CC = "참조인메일주소"
mail.Subject = "메일 제목"
mail.HTMLBody = "<span style='font: normal 10pt ""Malgun Gothic""; color: #1f4e79;'>메일 본문<br><br>끝"
mail.Display(True)

만약 Display 대신에 아래와 같이 Send를 이용하면 메일 팝업을 띄우지 않고 바로 메일을 전송하게 된다.

mail.Send()

Go로 Outlook 메일 팝업 띄우기

아래 예제는 Go로 OLE를 이용하여 Outlook 메일 팝업을 띄우는 예제이다. (Outlook이 실행 중이 아니어도 됨)

package main

import (
    "github.com/go-ole/go-ole"
    "github.com/go-ole/go-ole/oleutil"
)

func main() {
    ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED)
    unknown, _ := oleutil.CreateObject("Outlook.Application")
    outlook, _ := unknown.QueryInterface(ole.IID_IDispatch)
    mail := oleutil.MustCallMethod(outlook, "CreateItem", 0)
    oleutil.MustPutProperty(mail.ToIDispatch(), "To", "수신인메일주소")
    oleutil.MustPutProperty(mail.ToIDispatch(), "CC", "참조인메일주소")
    oleutil.MustPutProperty(mail.ToIDispatch(), "Subject", "메일 제목")
    oleutil.MustPutProperty(mail.ToIDispatch(), "HTMLBody", "<span style='font: normal 10pt \"Malgun Gothic\"; color: #1f4e79;'>메일 본문")
    oleutil.MustCallMethod(mail.ToIDispatch(), "Display", true)
    ole.CoUninitialize()
}

만약 Display 대신에 아래와 같이 Send를 이용하면 메일 팝업을 띄우지 않고 바로 메일을 전송하게 된다.

oleutil.MustCallMethod(mail.ToIDispatch(), "send")

수신자를 여러 명 넣을 경우에는 수신자에 Outlook에서와 마찬가지로 ;로 여러 명을 구분하여 세팅하면 된다.

파일을 첨부하려면 아래 예와 같이 하면 된다.

attach := oleutil.MustGetProperty(mail.ToIDispatch(), "Attachments")
oleutil.MustCallMethod(attach.ToIDispatch(), "Add", fileNamePath)

만약에 새로운 메일 대신에 기존에 받은 메일에 대한 reply 메일을 보내려면 mail 객체를 아래 예와 같이 얻으면 된다.

mail, _ := oleutil.GetProperty(item.ToIDispatch(), "Reply")

Outlook OLE 문서

참고로 위에서 사용한 Outlook OLE 문서는 https://docs.microsoft.com/en-us/office/vba/api/outlook.mailitem 페이지에서 확인할 수 있다.
이 페이지에서 Methods 목록은 호출할 수 있는 함수들로, 위 예에서는 DisplaySend를 이용하였다. 또 Properties 목록은 설정할 수 있는 항목들로, 위 예에서는 To, CC, Subject, HTMLBody 등을 사용하였다.

Python OLE로 Outlook 메일 읽기

Python에서 OLE를 이용하여 Outlook 메일을 읽고자 하는 경우에는 아래와 같이 구현할 수 있다.

import win32com.client

olFolderInbox = 6

outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
inboxfolder = outlook.GetDefaultFolder(olFolderInbox)
messages = inboxfolder.Items
print("Total inbox mail: " + str(messages.count))

i = 1
for ms in messages:
    print("[" + str(i) + "]")
    print("sender: " + ms.SenderName)
    print("to: " + ms.To)
    print("subject: " + ms.Subject)
    print(ms.Body)
    print('\n')
    i = i + 1

Go OLE로 Outlook 메일 읽기

Go로 OLE를 이용하여 Outlook 메일을 읽고자 하는 경우에는 아래와 같이 구현할 수 있다.

package main

import (
    "fmt"
    "github.com/go-ole/go-ole"
    "github.com/go-ole/go-ole/oleutil"
)

const olFolderInbox = 6

func main() {
    ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED)
    unknown, _ := oleutil.CreateObject("Outlook.Application")
    outlook, _ := unknown.QueryInterface(ole.IID_IDispatch)
    namespace := oleutil.MustCallMethod(outlook, "GetNamespace", "MAPI").ToIDispatch()
    folder := oleutil.MustCallMethod(namespace, "GetDefaultFolder", olFolderInbox).ToIDispatch()
    contacts := oleutil.MustCallMethod(folder, "Items").ToIDispatch()
    count := oleutil.MustGetProperty(contacts, "Count").Value().(int32)
    for i := 1; i <= int(count); i++ {
        item, err := oleutil.GetProperty(contacts, "Item", i)
        if err != nil || item.VT != ole.VT_DISPATCH {
            continue
        }
        sender, _ := oleutil.GetProperty(item.ToIDispatch(), "SenderName")
        to, _ := oleutil.GetProperty(item.ToIDispatch(), "To")
        subject, _ := oleutil.GetProperty(item.ToIDispatch(), "Subject")
        body, _ := oleutil.GetProperty(item.ToIDispatch(), "Body")
        fmt.Println("=== MAIL", i, " ===")
        fmt.Println("sender:", sender.ToString())
        fmt.Println("to:", to.ToString())
        fmt.Println("subject:", subject.ToString())
        fmt.Println(body.ToString())
    }
    ole.CoUninitialize()
}

결론

사실 OLE는 오래전에 Windows 프로그래밍 할 때에도 들어는 봤었는데 필요한 경우가 없어서 잊어버리고 있었는데, 의외로 이렇게 Go나 Python으로 MS Office 등을 제어할 때에도 아주 유용한 수단임을 알게 되었다. 💡
또한 OLE를 이용하게 되면 Outlook의 ID/PW를 입력하지 않아도 되므로, Outlook 메일을 다루는 Python/Go 등의 소스를 배포하는 경우에 좀 더 편리해지는 장점이 있다.

카테고리: ,

업데이트: