이메일
워커에서 메일을 보내고, 프로젝트 주소로 온 메일을 워커가 수신하게 하세요. runlot이 발신 평판을 관리하고 수신 메일 서버를 운영합니다.
모든 프로젝트에는 메일 주소가 하나씩 있습니다. org acme의 shop 프로젝트라면:
| 주소 | |
|---|---|
| 발신 | [email protected] |
| 수신 | *@shop.acme.runlot.email — 로컬 파트는 무엇이든 가능합니다 |
주소는 대시보드의 Email 탭이나 runlot email에서 확인할 수 있습니다.
발신
export default {
async fetch(request, env) {
await env.email.send({
to: "[email protected]", // string or array (up to 10)
subject: "Order received",
text: "Thanks. Shipping soon.",
html: "<p>Thanks. Shipping soon.</p>", // text or html is required
replyTo: "[email protected]", // optional
});
return new Response("ok");
},
};기본값에는 from이 없습니다 — 발신자는 프로젝트 주소입니다. 직접 보유한 도메인으로 보내려면 다음 절을 보세요. 호출은 { id }를 반환하고 실패 시 예외를 던집니다 — e.status는 HTTP 상태 코드입니다 (429는 시간별 한도를 의미합니다).
직접 보유한 도메인으로 보내기
도메인을 추가하고 레코드 세 개를 공개하면 from이 동작합니다.
runlot email domain add acme.com공개할 레코드가 출력됩니다. 이 중 세 개는 필수이며, 없으면 확인이 끝나지 않습니다.
| 종류 | 이름 | 값 |
|---|---|---|
| CNAME | <token1>._domainkey.acme.com | <token1>.dkim.amazonses.com |
| CNAME | <token2>._domainkey.acme.com | <token2>.dkim.amazonses.com |
| CNAME | <token3>._domainkey.acme.com | <token3>.dkim.amazonses.com |
나머지 두 개는 권장 사항이고, 확인이 이것을 기다리지 않습니다.
| 종류 | 이름 | 값 |
|---|---|---|
| TXT | acme.com | v=spf1 include:amazonses.com ~all |
| TXT | _dmarc.acme.com | v=DMARC1; p=none; adkim=r; aspf=r |
SPF 레코드를 소프트 페일(~all)로 두는 데에는 이유가 있습니다. 보유한 도메인은 이미 다른 곳에서도 메일을 보내고 있을 가능성이 큰데, 저희가 하드 페일을 얹으면 그 메일이 끊깁니다. 더 조이는 것은 직접 하시면 됩니다.
runlot email domain verify acme.com확인 중인 도메인은 저희도 1분마다 다시 확인합니다. 레코드를 공개해 두고 다른 일을 하셔도 됩니다. DNS가 퍼지는 데에는 몇 분에서 몇 시간이 걸리고, 72시간 동안 레코드가 보이지 않으면 실패로 표시되며 다시 추가하시면 됩니다.
해당 도메인의 존을 저희가 호스팅한다면(runlot dns) --publish를 붙여 주세요. 레코드를 대신 적어 드립니다. 직접 적으신 레코드는 덮어쓰지 않습니다.
verified로 바뀌면 이렇게 쓸 수 있습니다.
await env.email.send({
from: "[email protected]", // 확인된 도메인의 로컬 파트는 자유입니다
to: "[email protected]",
subject: "Order received",
text: "Thanks. Shipping soon.",
});확인된 도메인의 서브도메인도 됩니다 — [email protected]은 acme.com이 덮습니다. 그 밖의 도메인을 from에 쓰면 from_not_verified로 거절되고, 아직 확인 중인 도메인도 마찬가지입니다.
runlot email domain list # 도메인과 상태
runlot email domain rm acme.com # 삭제 (admin)수신은 여전히 *@<project>.<org>.runlot.email입니다. 보유한 도메인의 MX를 저희 쪽으로 돌리는 것은 아직 지원하지 않습니다.
수신
export default {
async fetch(request, env) { /* … */ },
async email(message, env, ctx) {
// message.from, message.to, message.headers (Headers), message.raw (ReadableStream), message.rawSize
const raw = await new Response(message.raw).text();
await env.db.query("insert into inbox (from_addr, to_addr, raw) values ($1, $2, $3)",
[message.from, message.to, raw]);
// To refuse — the sender sees this text
if (message.headers.get("authentication-results")?.includes("spf=fail")) {
message.setReject("SPF check failed");
}
},
};email 핸들러는 수신한 메시지마다 한 번씩 호출됩니다. message.raw는 헤더를 포함한 전체 메시지입니다 — 첨부 파일에는 postal-mime 같은 파서를 사용하세요.
핸들러를 내보내지 않는 프로젝트로 보낸 메일은 반송됩니다. 핸들러가 예외를 던지면 발신자가 나중에 재시도합니다 (일시적 실패로 처리됩니다).
제한 사항
| 값 | |
|---|---|
| 시간당 발신 수 | Free 30 · Pro 300 — 로그인 메일(env.auth)과 공유됩니다 |
| 메시지당 수신자 수 | 10 |
| 시간당 수신 수 | 600 |
| 수신 메시지 크기 | 25 MiB |
| 핸들러 실행 시간 | 30초 |
발신 한도를 초과하면 429를 받고, 수신 한도를 초과하면 발신자에게 나중에 다시 시도하라는 응답이 전달됩니다.
발신·수신 내역 확인
발신·수신된 모든 메시지는 대시보드의 Email 탭에서 한 줄로 표시됩니다: 시간, 방향, from, to, 제목, 결과, 크기. 줄을 클릭하면 헤더와 본문을 볼 수 있고, 원본 메시지는 .eml로 다운로드됩니다. HTML 본문은 렌더링되지 않고 소스로 표시됩니다.
| 요금제 | 보존 기간 |
|---|---|
| 무료 | 30일 |
| 프로 | 365일 |
runlot email log # latest 50
runlot email log --direction in # received only
runlot email log --cursor <cursor> # next page
runlot email raw <mailId> > mail.eml # raw message수신 메일의 결과는 핸들러가 한 작업입니다 — 처리됨, 거부됨, 핸들러 없음, 핸들러 오류. 발신 메일의 결과는 릴레이가 수락했는지 여부이며, 수락하지 않았다면 그 이유입니다.
여기 없는 것
- 보유한 도메인으로 수신. 발신은 위에서 됩니다. 보유한 도메인의 MX는 아직 지원하지 않습니다.
- 발신 시 첨부 파일.
text와html만 지원합니다. - 뉴스레터. 메시지당 수신자 10명이 절대 상한입니다.
forward()/reply(). 둘 다env.email.send로 처리할 수 있습니다.
CLI
runlot email # address and sent/received counts for the last hour and 24 hours
runlot email --json
runlot email delete # turn off (admin). Mail to this address will bounce
runlot email domain add acme.com [--publish] # send from your own domain
runlot email domain list # domains and their state
runlot email domain verify acme.com # re-check now
runlot email domain rm acme.com # remove (admin)