こんなメールサーバーが欲しい
普通に使うメールはOutlook+ExchangeOnlineだけど、
自動システムとか内部からのメール送信の一部はオンプレ環境にSMTPサーバーを置き送信を行う必要がある。
必要になったのはこんな機能のあるSMTPサーバー
- ExchangeOnlineでの送信
SMTPで受け取ったメールをExchangeOnlineの非SMTPで送信する。
SMTPでのログインユーザーでどのアカウントを使うかは決まる - SMTPリレーとして送信
その場合に、 STARTTLS対応・ DKIM署名付与 - どちらになるかはSMTP認証時のユーザーで決定する
こんなの欲しいが、少し探した限りでは見つからなかった。
つくる
ExchangeOnlineでの送信に関しては
SMTPに関しては
nodemailerはSMTPサーバーが必要なのが基本、
一応direct-transportもあるが、使ってみたらちょっと問題があったので
transporterを作ることにした。
動作作るならとMTA-STSにも対応させた。
nodemailerはdkim署名とSMTP接続の処理に使う。
SMTP接続前の処理は自作。
内容
全部コードはのせられないけど一部の処理を載せる、
- メールアドレスからMXサーバーの特定
アドレスのホスト部分のDNSからMXの候補を探し出す。
CNAME,MX,A,AAAAを見て、優先度設定して配列に入れていく
getMX = async (domain:string,cnameList?:Array<string>):Promise<undefined|Array<MxRecord>> => {
if(!domain){
return;
}
const cnames:Array<string> = cnameList||[];
const lookupLimit = 4;
//CNAME
const cnameRR = await this._dnsResolve("CNAME",domain);
if(cnameRR.record && typeof cnameRR.record[0] === "string"){
if(cnames.includes(domain)){
return;
}
cnames.push(cnameRR.record[0]);
if(cnames.length > lookupLimit){
return;
}
const mxList = await this._getMX(cnameRR.record[0],cnames);
if(!mxList){
return;
}
return [...mxList];
}
if(cnameRR.err?.temporary){
return;
}
//MX
const mxRR = await this._dnsResolve("MX",domain);
if(mxRR.mxrecord && mxRR.mxrecord.length > 0){
return [...mxRR.mxrecord];
}
if(cnameRR.err?.temporary){
return;
}
//A
const aRR = await this._dnsResolve("A",domain);
if(aRR.record){
return [{"exchange":domain,"priority":0}];
}
if(cnameRR.err?.temporary){
return;
}
//AAAA
const aaaaRR = await this._dnsResolve("AAAA",domain);
if(aaaaRR.record){
return [{"exchange":domain,"priority":0}];
}
if(cnameRR.err?.temporary){
return;
}
}
- MXのリスト順に送信を実行する
その際にMXサーバーに対してMTA-STAの検証をする
MTA-STSでTLS必須なら、SMTP接続でTLS検証必須にする。
//略
for(const mx of mxList){
const mtasts = await this.verifyMTASTS(sendAddressDomain.domain,mx.exchange);
if(mtasts){
TLS.DEFAULT_MIN_VERSION = "TLSv1.2";
}
const smtpCon = new SMTPConnectionSync({
"host":mx.exchange,
"port":25,
"secure":false,
"requireTLS":mtasts,
"tls": {
"rejectUnauthorized": mtasts,
"servername":mx.exchange,
}
});
try{
if(await smtpCon.connect()){
continue;
}
const sendResult = await smtpCon.send({"to":sendAddressDomain.sendAddress,"from":currentQueue.envelopFrom},currentQueue.messege);
await smtpCon.quit();
if(sendResult.info && sendResult.info.rejected.length > 0){
this.pushRetryQueue(currentQueue,{"domain":sendAddressDomain.domain,"sendAddress":sendResult.info.rejected});
}
log.debug("Done.");
//送信成功、MXリスト処理から抜ける
sendMX = true;
break;
}catch(err){
log.error(err);
continue;
}finally{
//略
}
}
//略
if(!sendMX){
//全MXへの処理に失敗、再送キューに入れる
this.pushRetryQueue(currentQueue,sendAddressDomain);
}
//略
- 送信失敗時の再送処理
これは送信内容をキューに入れて、最初は待機時間=0で即処理、成功したらそのまま終わり、
失敗したら待機時間をセットしてまたキューに入れる。
といった感で処理するようにした。
どうせ、内部でしか使わないし、頻度低いし、で結構適当に作ったけど
それなりに上手く動いている。