Dalam tutorial kali ini saya terangkan cara nak gabungkan Amazon SES Gmail. Klik video di atas untuk langkah demi langkah pemasangannya.
Di dalam video ini juga saya terangkan bagaimana untuk kita dapatkan Amazon SES SMTP.
Di dalam bahagian lain sebagai contoh untuk sistem autoresponder, saya menggunakan Sendy sebagai platform.
Sendy memerlukan penggunaan Amazon SES SMTP ini. Ada langkah yang kita perlu buat untuk melepasi fasa sandbox.
Mohon maaf seandainya rakaman ini terlalu panjang, ini adalah rakaman pertama saya sempena Perintah Kawalan Pergerakan (PKP) oleh Kerajaan Malaysia demi memutuskan rantaian penyebaran virus Covid19.
Jadi dengan menggunakan khidmat Amazon SES + Gmail ini, kita boleh terima dan hantar email menggunakan gmail sama seperti penggunaan kita di mailgun sebelum ini.
Pemilihan Region SES
Ini adalah langkah pertama yang paling penting.
Sebelum teruskan dengan langkah seterusnya, pada peringkat ini kena pastikan kita memilih region yang tepat!
Hanya ada beberapa region sahaja yang dibenarkan dalam Receiving Rule:
- US East (N. Virginia) us-east-1
- US West (Oregon) us-west-2
- Asia Pacific (Mumbai) ap-south-1
- Asia PAcific (Sydney) ap-southeast-2
- Europe (Frankfurt) eu-central-1
- Europe (Ireland) eu-west-1
Jika tersilap isi, kena ulang semula proses daripada awal. Buang masa dan tenaga nanti.
Senarai Kod Terlibat Dalam Amazon SES Gmail
DNS Record
_dmarc
v=DMARC1; p=reject; sp=none; rf=afrf; pct=100; ruf=mailto:info@EXAMPLE.COM; ri=86400
EXAMPLE.COM
v=spf1 a mx include:amazonaws.com include:amazonses.com ~all
Tukarkan “example.com” dengan domain anda. Pastikan dibuat pada jenis record TXT ya.
Set MAIL FROM Domain
Bahagian ni juga penting saya hampir terlupa dan tidak menerangkan tentang perkara ini di dalam video di atas.
Kenapa perlu set perkara ini? Ini membolehkan setiap email yang akan diterima oleh penerima dapat melindungi domain amazonses.com.
Sebelum kita set MAIL FROM ini, email kita akan kelihatan seperti ini:
Di bahagian mailed-by tu masih kelihatan domain amazonses.com. Jadi, jika nak ubah domain tersebut, boleh ikut langkah berikut dahulu. Kemudian baru setup Lambda & Gmail.
Jadi pertama sekali pergi di bahagian domain setting di Amazon SES.
S3 Bucket Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSESPuts",
"Effect": "Allow",
"Principal": {
"Service": "ses.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::BUCKET-NAME/*",
"Condition": {
"StringEquals": {
"aws:Referer": "AWS ACCOUNT ID"
}
}
}
]
}
Ada dua perkara yang perlu ditukar sebelum disimpan, iaitu nama bucket dan juga AWS Account ID.
IAM Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": "ses:SendRawEmail",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::BUCKET-NAME/*"
}
]
}
Sama juga, sebelum disimpan perlu ditukar nama S3 Bucket.
Terakhir sekali adalah berkaitan dengan kod yang ditulis oleh Tuan Marius. Kita akan menggunakan lambda dan juga node.js 10x. Jangan tersilap ya.
Kod ini perlu diletakkan di dalam function lambda ya. Kod asal boleh rujuk di github beliau.
Saudara hanya perlu ubah beberapa baris kod sahaja di bahagian defaultConfig. Jangan lupa salin dahulu kod Nodejs penuh dibawah kemudian ubah baris kod seperti yang saya tunjuk dibawah ini:
var defaultConfig = {
fromEmail: "noreply@gulihijau.com",
subjectPrefix: "",
emailBucket: "gulihijau",
emailKeyPrefix: "gulihijau/",
forwardMapping: {
"hello@gulihijau.com": [
"veenet2020@gmail.com"
]
}
};
Kod Nodejs Penuh (Lambda)
"use strict";
var AWS = require('aws-sdk');
console.log("AWS Lambda SES Forwarder // @arithmetric // Version 4.2.0");
// Configure the S3 bucket and key prefix for stored raw emails, and the
// mapping of email addresses to forward from and to.
//
// Expected keys/values:
//
// - fromEmail: Forwarded emails will come from this verified address
//
// - subjectPrefix: Forwarded emails subject will contain this prefix
//
// - emailBucket: S3 bucket name where SES stores emails.
//
// - emailKeyPrefix: S3 key name prefix where SES stores email. Include the
// trailing slash.
//
// - forwardMapping: Object where the key is the lowercase email address from
// which to forward and the value is an array of email addresses to which to
// send the message.
//
// To match all email addresses on a domain, use a key without the name part
// of an email address before the "at" symbol (i.e. `@example.com`).
//
// To match a mailbox name on all domains, use a key without the "at" symbol
// and domain part of an email address (i.e. `info`).
var defaultConfig = {
fromEmail: "noreply@gulihijau.com",
subjectPrefix: "",
emailBucket: "gulihijau",
emailKeyPrefix: "gulihijau/",
forwardMapping: {
"hello@gulihijau.com": [
"veenet2020@gmail.com"
]
}
};
/**
* Parses the SES event record provided for the `mail` and `receipients` data.
*
* @param {object} data - Data bundle with context, email, etc.
*
* @return {object} - Promise resolved with data.
*/
exports.parseEvent = function(data) {
// Validate characteristics of a SES event record.
if (!data.event ||
!data.event.hasOwnProperty('Records') ||
data.event.Records.length !== 1 ||
!data.event.Records[0].hasOwnProperty('eventSource') ||
data.event.Records[0].eventSource !== 'aws:ses' ||
data.event.Records[0].eventVersion !== '1.0') {
data.log({message: "parseEvent() received invalid SES message:",
level: "error", event: JSON.stringify(data.event)});
return Promise.reject(new Error('Error: Received invalid SES message.'));
}
data.email = data.event.Records[0].ses.mail;
data.recipients = data.event.Records[0].ses.receipt.recipients;
return Promise.resolve(data);
};
/**
* Transforms the original recipients to the desired forwarded destinations.
*
* @param {object} data - Data bundle with context, email, etc.
*
* @return {object} - Promise resolved with data.
*/
exports.transformRecipients = function(data) {
var newRecipients = [];
data.originalRecipients = data.recipients;
data.recipients.forEach(function(origEmail) {
var origEmailKey = origEmail.toLowerCase();
if (data.config.forwardMapping.hasOwnProperty(origEmailKey)) {
newRecipients = newRecipients.concat(
data.config.forwardMapping[origEmailKey]);
data.originalRecipient = origEmail;
} else {
var origEmailDomain;
var origEmailUser;
var pos = origEmailKey.lastIndexOf("@");
if (pos === -1) {
origEmailUser = origEmailKey;
} else {
origEmailDomain = origEmailKey.slice(pos);
origEmailUser = origEmailKey.slice(0, pos);
}
if (origEmailDomain &&
data.config.forwardMapping.hasOwnProperty(origEmailDomain)) {
newRecipients = newRecipients.concat(
data.config.forwardMapping[origEmailDomain]);
data.originalRecipient = origEmail;
} else if (origEmailUser &&
data.config.forwardMapping.hasOwnProperty(origEmailUser)) {
newRecipients = newRecipients.concat(
data.config.forwardMapping[origEmailUser]);
data.originalRecipient = origEmail;
}
}
});
if (!newRecipients.length) {
data.log({message: "Finishing process. No new recipients found for " +
"original destinations: " + data.originalRecipients.join(", "),
level: "info"});
return data.callback();
}
data.recipients = newRecipients;
return Promise.resolve(data);
};
/**
* Fetches the message data from S3.
*
* @param {object} data - Data bundle with context, email, etc.
*
* @return {object} - Promise resolved with data.
*/
exports.fetchMessage = function(data) {
// Copying email object to ensure read permission
data.log({level: "info", message: "Fetching email at s3://" +
data.config.emailBucket + '/' + data.config.emailKeyPrefix +
data.email.messageId});
return new Promise(function(resolve, reject) {
data.s3.copyObject({
Bucket: data.config.emailBucket,
CopySource: data.config.emailBucket + '/' + data.config.emailKeyPrefix +
data.email.messageId,
Key: data.config.emailKeyPrefix + data.email.messageId,
ACL: 'private',
ContentType: 'text/plain',
StorageClass: 'STANDARD'
}, function(err) {
if (err) {
data.log({level: "error", message: "copyObject() returned error:",
error: err, stack: err.stack});
return reject(
new Error("Error: Could not make readable copy of email."));
}
// Load the raw email from S3
data.s3.getObject({
Bucket: data.config.emailBucket,
Key: data.config.emailKeyPrefix + data.email.messageId
}, function(err, result) {
if (err) {
data.log({level: "error", message: "getObject() returned error:",
error: err, stack: err.stack});
return reject(
new Error("Error: Failed to load message body from S3."));
}
data.emailData = result.Body.toString();
return resolve(data);
});
});
});
};
/**
* Processes the message data, making updates to recipients and other headers
* before forwarding message.
*
* @param {object} data - Data bundle with context, email, etc.
*
* @return {object} - Promise resolved with data.
*/
exports.processMessage = function(data) {
var match = data.emailData.match(/^((?:.+\r?\n)*)(\r?\n(?:.*\s+)*)/m);
var header = match && match[1] ? match[1] : data.emailData;
var body = match && match[2] ? match[2] : '';
// Add "Reply-To:" with the "From" address if it doesn't already exists
if (!/^Reply-To: /mi.test(header)) {
match = header.match(/^From: (.*(?:\r?\n\s+.*)*\r?\n)/m);
var from = match && match[1] ? match[1] : '';
if (from) {
header = header + 'Reply-To: ' + from;
data.log({level: "info", message: "Added Reply-To address of: " + from});
} else {
data.log({level: "info", message: "Reply-To address not added because " +
"From address was not properly extracted."});
}
}
// SES does not allow sending messages from an unverified address,
// so replace the message's "From:" header with the original
// recipient (which is a verified domain)
header = header.replace(
/^From: (.*(?:\r?\n\s+.*)*)/mg,
function(match, from) {
var fromText;
if (data.config.fromEmail) {
fromText = 'From: ' + from.replace(//, '').trim() +
' ';
} else {
fromText = 'From: ' + from.replace('', '') +
' ';
}
return fromText;
});
// Add a prefix to the Subject
if (data.config.subjectPrefix) {
header = header.replace(
/^Subject: (.*)/mg,
function(match, subject) {
return 'Subject: ' + data.config.subjectPrefix + subject;
});
}
// Replace original 'To' header with a manually defined one
if (data.config.toEmail) {
header = header.replace(/^To: (.*)/mg, () => 'To: ' + data.config.toEmail);
}
// Remove the Return-Path header.
header = header.replace(/^Return-Path: (.*)\r?\n/mg, '');
// Remove Sender header.
header = header.replace(/^Sender: (.*)\r?\n/mg, '');
// Remove Message-ID header.
header = header.replace(/^Message-ID: (.*)\r?\n/mig, '');
// Remove all DKIM-Signature headers to prevent triggering an
// "InvalidParameterValue: Duplicate header 'DKIM-Signature'" error.
// These signatures will likely be invalid anyways, since the From
// header was modified.
header = header.replace(/^DKIM-Signature: .*\r?\n(\s+.*\r?\n)*/mg, '');
data.emailData = header + body;
return Promise.resolve(data);
};
/**
* Send email using the SES sendRawEmail command.
*
* @param {object} data - Data bundle with context, email, etc.
*
* @return {object} - Promise resolved with data.
*/
exports.sendMessage = function(data) {
var params = {
Destinations: data.recipients,
Source: data.originalRecipient,
RawMessage: {
Data: data.emailData
}
};
data.log({level: "info", message: "sendMessage: Sending email via SES. " +
"Original recipients: " + data.originalRecipients.join(", ") +
". Transformed recipients: " + data.recipients.join(", ") + "."});
return new Promise(function(resolve, reject) {
data.ses.sendRawEmail(params, function(err, result) {
if (err) {
data.log({level: "error", message: "sendRawEmail() returned error.",
error: err, stack: err.stack});
return reject(new Error('Error: Email sending failed.'));
}
data.log({level: "info", message: "sendRawEmail() successful.",
result: result});
resolve(data);
});
});
};
/**
* Handler function to be invoked by AWS Lambda with an inbound SES email as
* the event.
*
* @param {object} event - Lambda event from inbound email received by AWS SES.
* @param {object} context - Lambda context object.
* @param {object} callback - Lambda callback object.
* @param {object} overrides - Overrides for the default data, including the
* configuration, SES object, and S3 object.
*/
exports.handler = function(event, context, callback, overrides) {
var steps = overrides && overrides.steps ? overrides.steps :
[
exports.parseEvent,
exports.transformRecipients,
exports.fetchMessage,
exports.processMessage,
exports.sendMessage
];
var data = {
event: event,
callback: callback,
context: context,
config: overrides && overrides.config ? overrides.config : defaultConfig,
log: overrides && overrides.log ? overrides.log : console.log,
ses: overrides && overrides.ses ? overrides.ses : new AWS.SES(),
s3: overrides && overrides.s3 ?
overrides.s3 : new AWS.S3({signatureVersion: 'v4'})
};
Promise.series(steps, data)
.then(function(data) {
data.log({level: "info", message: "Process finished successfully."});
return data.callback();
})
.catch(function(err) {
data.log({level: "error", message: "Step returned error: " + err.message,
error: err, stack: err.stack});
return data.callback(new Error("Error: Step returned error."));
});
};
Promise.series = function(promises, initValue) {
return promises.reduce(function(chain, promise) {
if (typeof promise !== 'function') {
return Promise.reject(new Error("Error: Invalid promise item: " +
promise));
}
return chain.then(promise);
}, Promise.resolve(initValue));
};
Hantar Email WordPress Guna SMTP Amazon SES
Untuk menghidupkan fungsi penghantaran email oleh WordPress, saudara memerlukan maklumat yang dibekalkan oleh Amazon SES di ruangan SMTP.
Hanya perlu klik butang Create My SMTP Credentials. Kemudian dapatkan tiga maklumat iaitu:
- email-smtp.eu-west-1.amazonaws.com (Bergantung kepada region SES anda, dicatatkan pada ruangan server name)
- Smtp Username
- Smtp Password
Selepas itu, saudara perlu memasang plugin di wordpress, boleh buat carian beberapa plugin yang menghidupkan fungsi SMTP.
Tapi dalam tutorial ini saya menggunakan plugin WordPress Post SMTP, guna yang mana-mana pun sama sahaja. Cuma perlu lihat ada konflik atau tidak di wordpress kita.
Klik pada butang Start the Wizard untuk meneruskan pemasangan.
Daripada situ anda akan dibawa ke satu halaman untuk memasukkan maklumat SMTP yang diberikan oleh Amazon SES.
Kemudian, selesai. Mudah sahaja untuk setup email SMTP ini.
Boleh cuba test email untuk melihat berjaya atau tidak WordPress menggunakan fungsi SMTP dengan maklumat yang kita gunakan dalam Amazon SES.
Terima kasih. Saya banyak belajar. Dulu pakai MailGun sekajrang nak beralih dan cuba main email marketing.
Video fu di https://www.youtube.com/watch?v=R1Sg8rB4zd0 pun mantap sekali