이메일 보내기
`emails.send`: 메시지 하나를 지금 또는 나중에 보냅니다.
emails.send
email = client.emails.send( from: {email: "[email protected]", name: "Acme Billing"}, to: ["[email protected]", "Grace <[email protected]>"], cc: "[email protected]", bcc: [{email: "[email protected]"}], replyTo: "[email protected]", subject: "Your September invoice", html: "<p>Invoice attached.</p>", text: "Invoice attached.", headers: {"X-Campaign" => "invoices"}, attachments: [{filename: "invoice.pdf", content: Pathname("invoice.pdf")}], threadId: "CAHk7pQ2x9LmZ4-mail.example.com", scheduledAt: "PT1H", tags: {order: "4021"}, tracking: {opens: true, clicks: true}) puts email[:id], email[:status]to, cc, bcc는 수신자 하나 또는 수신자의 Array를 받으며, 하나만 주면 자동으로 감싸 줍니다. 각각은 주소만 쓰거나 Name <addr@host>, 또는 email과 name을 가진 Hash일 수 있습니다.
메시지는 키워드 인자나 Hash 하나로 전달합니다. Hash와 나란히 준 키워드 인자는 그 Hash에 병합되며 같은 필드를 지정하면 키워드 인자가 우선하므로, client.emails.send(message, subject: "Re: your invoice")는 앞서 만든 메시지의 필드 하나만 바꿉니다. 키는 API의 이름을 그대로 쓰므로 replyTo와 scheduledAt은 camelCase로 남지만, idempotency_key:와 api_key:는 호출의 옵션이며 메시지의 일부가 아닙니다.
매개변수
fromString or Hash필수- 발신자입니다. 주소만 쓰거나 `Name <addr@host>`, 또는 `email`과 `name`을 가진 Hash로 지정합니다. 이 키가 발신자로 쓸 수 있는 주소여야 하며, 그렇지 않으면 호출은 403 `from_address_forbidden`을 발생시킵니다. 기본 발신자는 없으므로, 발송은 항상 발신 주소를 직접 지정합니다.
toString, Hash or Array필수- 수신자 하나 또는 수신자의 Array이며, 하나만 주면 자동으로 감싸 줍니다. `to`, `cc`, `bcc`를 합쳐 최대 50개이며, 그보다 많으면 422 `too_many_recipients`입니다.
ccString, Hash or Array- 수신자 50명 제한에 포함됩니다.
bccString, Hash or Array- 수신자마다 봉투가 하나씩 전송되므로, 다른 사람이 받는 바이트에는 결코 이름이 나타나지 않습니다. 이것도 50개에 포함됩니다.
replyToString or Hash- 주소 하나이며, Reply-To 헤더로 전송됩니다.
subjectString- 최대 998자로 RFC 5322의 줄 길이 제한입니다. 기본값은 비어 있으며, 빈 제목은 템플릿이나 초안의 제목으로 대체됩니다.
htmlString- `html`, `text`, `draftId`, `template` 중 하나는 필수입니다. `html`과 `text`가 모두 주어지면 수신자가 보는 것은 HTML입니다. 최대 1,000,000자입니다.
textString- 일반 텍스트 부분으로, 최대 1,000,000자입니다.
templateHash- 저장된 템플릿을 서버에서 렌더링합니다: id 또는 slug를 받는 `id`와, 선택적인 `version`(Integer), `props`, `slots`를 가진 Hash입니다. `version`은 버전을 고정합니다. 생략하면 요청이 수락되는 시점에 게시되어 있는 것을 사용합니다. 알 수 없거나 누락된 prop은 메시지의 빈칸이 아니라 422가 됩니다.
draftIdString- 저장된 초안을 작성된 그대로 이 봉투로 보냅니다. `template`이나 `translate`와 함께 쓸 수 없습니다.
headersHash- 헤더 이름에서 String 값으로의 매핑으로, `X-*`, `List-*`, Reply-To, Precedence, Auto-Submitted, Importance, Priority, Feedback-ID로 제한됩니다. 전송 계층이 스스로 설정하는 것은 조용히 버려지지 않고 422 `reserved_header`로 거부됩니다.
attachmentsArray<Hash>- 각각 `filename`, `content`, 선택적인 `contentType`을 가진 Hash이거나, `files.upload`로 올린 파일처럼 워크스페이스에 이미 있는 파일을 가리키는 `fileId`만 가진 Hash입니다. `content`에 바이트를 전달하면 base64로 인코딩해 줍니다. 파일은 20개까지이며 인라인 파일은 디코딩 후 합계 5 MB로 제한됩니다. 저장된 파일은 더 클 수 있고 다운로드 링크로 전달됩니다.
attachmentDeliveryString- `mime`, `link`, `auto` 중 하나입니다. `auto`는 파일 도메인이 활성화된 도메인에서 2 MB를 넘기면 다운로드 링크로, 그렇지 않으면 메시지 안에 담아 보냅니다. 생략하면 메일함 설정이 적용되며, 그 기본값은 `auto`입니다.
threadIdString- 기존 스레드에 답장합니다. 전송 계층이 In-Reply-To와 References를 씁니다.
scheduledAtTime, DateTime or String- UTC의 ISO 8601 시각으로 전송되는 Time 또는 DateTime, String으로 된 ISO 8601 시각, 또는 `PT1H` 같은 기간입니다. 최대 1년 뒤까지이며 과거는 안 됩니다. `cancellableForSeconds`와 함께 쓸 수 없습니다. Ruby의 Date는 날짜만 있는 값으로 전송되고 API는 이를 그날의 UTC 자정으로 읽으므로, 시각이 중요할 때는 Time을 전달하세요.
cancellableForSecondsInteger- 0에서 900까지입니다. 즉시 발송에 대한 실행 취소 시간으로, 작성기의 실행 취소 메커니즘을 하드코딩하는 대신 노출한 것입니다.
tagsHash- 라벨은 최대 10개이며, 키는 영문자, 숫자, `_`, `-`로 이루어진 1~64자, 값은 최대 256자의 String입니다. 읽을 때마다 그대로 돌려주며 해석하지 않습니다.
signatureBoolean- 이 메시지에 보내는 주소의 서명을 붙일지 여부입니다. 그 주소 자체의 서명, 없으면 캐치올이 받은 주소일 때 캐치올의 서명, 그것도 없으면 OpenEmail 바닥글입니다(그 주소가 끄지 않은 경우). 생략하면 `html` 본문은 쓴 그대로 서명 없이 나가고, `text`만 있는 본문에는 서명이 붙습니다. 영수증, 비밀번호 재설정, 요약처럼 프로그램이 누군가를 대신해 보내는 메일에는 `false`를 지정하세요. 어느 것도 사람의 서명이 아래에 붙기를 원하지 않습니다. 템플릿 발송과 암호화된 발송에는 서명이 붙지 않습니다.
trackingHash- 선택적인 Boolean `opens`와 `clicks`를 가진 Hash로, 이 메시지에 열람 픽셀을 넣고 링크를 재작성할지 여부입니다. 발신 주소(또는 그 주소를 받아 낸 캐치올)에 대해 추적을 켜지 않았다면 꺼져 있으며, 여기서 어느 한쪽 키를 명시하면 주소 설정이 어떻든 그 메시지 하나에 대해 결정됩니다.
translateHash- 수신자의 언어로 보냅니다: `to`와 선택적인 `from`, `subject`, `includeOriginal`을 가진 Hash입니다. `to`는 코드, 영어 이름, 또는 해당 언어 자체의 이름을 받으며, `subject`와 `includeOriginal`은 둘 다 기본값이 true입니다. 요청이 수락될 때 확정되므로 예약된 메시지도 승인된 문구를 담고 나갑니다. `draftId`와 함께 쓰면 거부됩니다.
idempotency_keyString- 이 발송을 위한 직접 만든 키로, 영문자, 숫자, `_`, `.`, `:`, `-`로 이루어진 1~255자입니다. 없으면 클라이언트가 호출마다 키를 생성하므로 자체 재시도로 두 번 발송하는 일은 없으며, 지정하면 다른 프로세스에서 다시 실행된 발송이 반복되지 않고 재생됩니다.
api_keyString- 여러 워크스페이스를 대신해 발송하는 프로세스를 위해, 클라이언트의 키 대신 이 키로 발송합니다.
응답
Symbol 키를 가진 Hash이므로, email[:status]로 상태를 읽습니다.
idString- 발송 id로, `msg_` 뒤에 16진수 24자가 붙습니다. `get`, `cancel`, `reschedule`, `get_tracking`에 사용하세요.
statusString- queued, scheduled, sending, sent, partial, bounced, cancelled, failed 중 하나입니다. 호출이 반환되었다는 사실이 아니라 이 값을 읽으세요: 즉시 발송은 요청 안에서 전송되어 보통 `sent`, `partial`, `failed`로 돌아오고, 보류된 발송은 `queued`나 `scheduled`로 돌아옵니다. `partial`은 그 자체로 하나의 상태입니다: 일부 수신자는 이미 메시지를 받았고 되돌릴 수 없으므로, 재시도는 잘못된 대응이고 실패라고 보고하는 것은 거짓입니다.
modeString- `live` 또는 `test`: 어떤 종류의 키로 보냈는지입니다. 테스트 발송은 기록되지만 실제로 전송되지는 않습니다. `transport`가 `test`이고 상태는 `sent`로 나타나므로, 받은편지함이 아니라 응답을 기준으로 검증하세요.
fromString- 실제로 인가되어 전송에 사용된 주소이며, 요청한 주소와 항상 같지는 않습니다.
subjectString or nil- 보낸 그대로입니다.
messageIdString or nil- RFC 5322의 Message-ID입니다. MIME이 만들어지기 전까지는 nil입니다. 발송 서비스가 나가는 길에 헤더를 다시 쓰므로, 어떤 반송이나 배달 보고서도 이 값을 담지 않습니다. 이벤트가 담고 오는 것은 `id`입니다.
threadIdString or nil- 메시지가 들어간 스레드입니다.
transportString or nil- 메시지가 어떤 경로로 나갔는지입니다. 발송 전에는 nil입니다.
attemptsInteger- 발송을 몇 번 시도했는지입니다.
lastErrorString or nil- 마지막 시도가 실패한 이유를 그대로 담습니다.
scheduledAtString or nil- 발송 예정인 ISO 8601 시각입니다.
cancellableUntilString or nil- 현재 시각이 이보다 앞이면 `cancel`이 여전히 동작합니다.
sentAtString or nil- 메시지가 나간 ISO 8601 시각입니다.
tagsHash- 보낸 값을 그대로 되돌려 준 것입니다.
sourceString- composer, api, mcp, ai, queue 중 어떤 표면이 요청했는지입니다. `api`가 이 클라이언트입니다.
createdAtString- 레코드가 기록된 ISO 8601 시각입니다.
replayedBoolean- Idempotency-Key가 이미 존재하는 발송과 일치할 때 true입니다. 새로 보내진 것은 없으며, 이것은 원래 메시지의 현재 상태입니다.
translationHash- 번역된 메시지에만, 그리고 저장된 요청 전체가 실리는 곳, 즉 이 응답과 `get`에만 있습니다. `language`, `languageName`, `detectedSourceLanguage`, `subject`, `includeOriginal`을 담으며, 언어 행 전체가 아니라 코드로 나타냅니다. 목록 행에는 결코 없으므로, 거기에 없다는 사실은 어느 쪽으로도 아무것도 말해 주지 않습니다.
수신자의 언어로
translate는 메시지가 나가기 전에 다른 사람의 언어로 다시 씁니다. 본문과, 끄지 않았다면 제목까지 API가 요청을 수락할 때 번역되고, 번역된 결과가 그대로 나갑니다. 번역을 만들어 내지 못하면 원래 쓴 언어로 보내는 대신 발송을 거부합니다.
email = client.emails.send( from: "[email protected]", to: "[email protected]", subject: "Your September invoice", html: "<p>Invoice attached. Payment is due on the 14th.</p>", translate: {to: "de"}) p email[:translation]그러면 email[:translation]은 {language: "de", languageName: "German", detectedSourceLanguage: "en", subject: true, includeOriginal: true}가 됩니다.
그렇게 나간 메일은 아무도 검토하지 않은 것입니다. emails.translate는 같은 왕복을 한 단계 앞에서 멈춘 것입니다. 사람에게 보여 주고 고치게 한 다음, 승인된 내용을 호출에 translate 없이 그대로 보내세요. 다시 전달하면 두 번 번역되어 그 수정이 버려집니다.
preview = client.emails.translate( subject: "Your September invoice", html: "<p>Invoice attached. Payment is due on the 14th.</p>", to: "de") puts preview.dig(:language, :native), preview[:subject], preview[:html]print "Send it as it is? [y/N] " if $stdin.gets.to_s.strip.casecmp?("y") client.emails.send( from: "[email protected]", to: "[email protected]", subject: preview[:subject], html: preview[:html] )endp OpenEmail::LANGUAGES.size current = client.languages.listp current.size p OpenEmail.resolve_language("Deutsch")&.fetch(:code)p OpenEmail.resolve_language("zh-TW")&.fetch(:code)p OpenEmail.language_by_code("DE")&.fetch(:native)p OpenEmail.rtl_language?("ar")이 줄들은 이 버전에 포함된 행의 수인 200, API가 지금 가진 행의 수, 그리고 "de", "zh-Hant", "Deutsch", true를 출력합니다. 이 표는 선택기 순서 그대로 OpenEmail::LANGUAGES로 번들에 포함되어 있으며, code, label, native, flag, rtl을 가진 Hash의 동결된 Array이므로 첫 요청 전에 선택기를 채울 수 있습니다. languages.list는 이 버전에 포함된 것이 아니라 현재 목록을 원하는 호출자를 위해, 같은 행을 통신으로 받아 일반 Array로 반환합니다. OpenEmail.resolve_language는 코드, 영어 이름, 자칭 이름, 별칭(zh-TW는 더 이상 목록에 없는 코드의 별칭입니다)을 받고 일치하는 것이 없으면 nil을 반환하며, OpenEmail.language_by_code는 대소문자를 무시하고 코드를 정확히 일치시킵니다. 행 중 16개는 오른쪽에서 왼쪽으로 씁니다. native, label, code를 함께 검색하고, native를 먼저 보여 주고, 코드를 저장하세요.
emails.translate는 자동으로 재시도되지 않습니다. 모델 호출을 소모하고 아무것도 쓰지 않으므로 멱등하게 만들 대상이 없고, 응답이 없던 요청을 재시도해 봐야 같은 답을 두 번 사는 것뿐입니다.
- API가 일치시킬 수 없는 언어는 무엇이든 보내기 전에
translate.to에 대한validation_error가 됩니다. - 30,000자를 넘으면
translation_too_long, 설치본에 AI가 구성되어 있지 않으면translation_not_configured, 워크스페이스가 오늘의 AI 작업을 다 썼으면 429ai_quota_exceeded(UTC 자정에 초기화되며 재시도하지 않습니다), 제공자가 응답하지 않으면translation_failed입니다. 어느 것도 번역되지 않은 메시지를 대신 보내지 않습니다. template과 함께 동작합니다. 번역되는 것은 렌더링된 결과이므로, 저장된 본문 하나가 고객이 읽는 모든 언어를 감당합니다. 문서 전체를 렌더링하는 템플릿은 doctype과<style>블록,@font-face규칙을 그대로 유지합니다. 모델에 가는 것은 본문뿐이고 나머지는 다시 그 주위에 붙습니다.<title>은 그대로 두는데, 어차피 표시하는 곳이 없습니다.- 재시도에 추가 비용은 없습니다. 번역은 멱등성 지문의 일부가 아니므로(지문에 들어가는 것은
translate를 포함한 요청입니다), 응답이 없던 발송을 같은Idempotency-Key로 재시도하면 두 번째로 번역해 보내는 대신 이미 존재하는 메시지를 재생합니다. - 큐에 있거나 예약된 번역 메시지는 승인된 문구를 유지합니다.
emails.reschedule로 시각은 옮길 수 있지만,emails.update는 새 문구를 409translation_locked로 거부하므로, 내용을 바꾸려면 취소하고 다시 보내야 합니다.
첨부 파일
content는 통신상에서 base64입니다. 바이트를 전달하면 자동으로 인코딩됩니다: File.binread가 반환하는 것 같은 바이너리 String, 열린 File 같은 IO, 또는 대신 읽어 주는 Pathname입니다.
attachments = [ {filename: "invoice.pdf", content: File.binread("invoice.pdf"), contentType: "application/pdf"}, {filename: "report.pdf", content: Pathname("report.pdf")}, {fileId: "file_6bb640f5b99e47deb758f1f5"}] client.emails.send( from: "[email protected]", to: "[email protected]", subject: "Your documents", text: "Both are attached.", attachments:)File.read가 반환하는 것처럼 텍스트로 태그된 String은 이미 base64인 것으로 간주되며, base64가 아니면 무엇이든 보내기 전에 ArgumentError를 발생시킵니다. 파일은 File.binread로 읽거나, 텍스트로 태그되어 도착한 바이트에는 .b를 호출하세요.
같은 인코딩이 다른 곳에서 필요하다면 OpenEmail.to_base64가 있습니다. 바이너리 String, IO, Pathname을 받아 줄바꿈 없는 엄격한 base64를 반환합니다.