CORS in AMP for Email
Cross-Origin Resource Sharing (CORS) is a mechanism that uses HTTP headers to tell browsers which origins are allowed access to a resource using XHR. AMP for Email extends this mechanism by adding HTTP headers that similarly tell email clients which senders are allowed access to these resources.
There are currently two versions of this mechanism. For the time being, it's recommended to support both on your server-side.
Version 2
When an email makes a request via amp-form
, amp-list
or any other XHR-based mechanism, the email client includes the following HTTP header:
AMP-Email-Sender
, set to the email address of the sender of the email.
It expects that the HTTP response contains the following header in return:
AMP-Email-Allow-Sender
with either the same value asAMP-Email-Sender
in the request, or*
indicating all sender emails are allowed.
Example
A user opens an email from sender@company.example
in their email client by going to https://emailclient.example/myinbox
. The email uses AMP for Email and loads data using an amp-list
from https://company.example/data.json
.
The email client sends an HTTP request to https://company.example/data.json
with the following headers set:
AMP-Email-Sender: sender@company.example
The email client expects that the HTTP response contains the following headers:
AMP-Email-Allow-Sender: sender@company.example
Alternatively, using *
in either header is allowed (but not recommended).
Version 1 (deprecated)
In version 1, the email client uses a query string parameter instead of an HTTP header to indicate the sender email. It also provides an Origin
header and requires the Access-Control-Allow-Origin
header in response, like CORS on websites.
When an email makes a request via amp-form
, amp-list
or any other XHR-based mechanism, the email client includes the following HTTP header:
Origin
with the value of the origin of the page used to display the email.
The URL also always has a query string with the __amp_source_origin
parameter set to the email address of the sender of the email.
It expects that the HTTP response contains the following headers:
Access-Control-Allow-Origin
with the same value asOrigin
in the requestAMP-Access-Control-Allow-Source-Origin
with the same value as the__amp_source_origin
query string parameter in the request.Access-Control-Expose-Headers
set toAMP-Access-Control-Allow-Source-Origin
Example
A user opens an email from sender@company.example
in their email client by going to https://emailclient.example/myinbox
. The email uses AMP for Email and loads data using an amp-list
from https://company.example/data.json
.
The email client sends an HTTP request to https://company.example/data.json?__amp_source_origin=sender@company.example
(notice the added query string) with the following header set:
Origin: https://emailclient.example
The email client expects that the HTTP response contains the following headers:
Access-Control-Allow-Origin: https://emailclient.example
AMP-Access-Control-Allow-Source-Origin: sender@company.example
Access-Control-Expose-Headers: AMP-Access-Control-Allow-Source-Origin
Note that this version does not support using *
in the AMP-Access-Control-Allow-Source-Origin
header.
Implementing CORS
These are the recommended steps to take on the server-side to implement CORS that supports both version 1 and 2:
When you receive an HTTP request, check if the Origin
and AMP-Email-Sender
HTTP headers are set.
- If the
AMP-Email-Sender
header is set:- Let senderEmail be the value of the
AMP-Email-Sender
header. - Check if senderEmail is an email address owned by you or one that you trust. If not, reject the request.
- Set the response header
AMP-Email-Allow-Sender
to senderEmail.
- Let senderEmail be the value of the
- If the
Origin
header is set, butAMP-Email-Sender
is not set:- Let requestOrigin be the value of the
Origin
header. - Set the response header
Access-Control-Allow-Origin
to requestOrigin. - Check if the URL contains the
__amp_source_origin
query string parameter. If not reject the request. - Let senderEmail be the value of the
__amp_source_origin
query string parameter. - Check if senderEmail is an email address owned by you or one that you trust. If not, reject the request.
- Set the response header
AMP-Access-Control-Allow-Source-Origin
to senderEmail. - Set the response header
Access-Control-Expose-Headers
toAMP-Access-Control-Allow-Source-Origin
.
- Let requestOrigin be the value of the
- If neither
Origin
norAMP-Email-Sender
are set, reject the request.
Example 1
Request sent by email client
GET /data.json?__amp_source_origin=sender@company.example HTTP/1.1
Host: company.example
Origin: https://emailclient.example
User-Agent: EmailClientProxy
Accept: application/json
Response headers expected
Access-Control-Allow-Origin: https://emailclient.example
Access-Control-Expose-Headers: AMP-Access-Control-Allow-Source-Origin
AMP-Access-Control-Allow-Source-Origin: sender@company.example
Explanation
Because the Origin
header was set, this request is using CORS version 1 and requires the three headers listed above to be set.
Example 2
Request sent by email client
GET /data.json HTTP/1.1
Host: company.example
AMP-Email-Sender: sender@company.example
User-Agent: EmailClientProxy
Accept: application/json
Response headers expected
AMP-Email-Allow-Sender: sender@company.example
Explanation
Because the AMP-Email-Sender
header was set, this request is using CORS version 2 and only requires the AMP-Email-Allow-Sender
header.
Example 3
Request sent by email client
GET /data.json?__amp_source_origin=sender@company.example HTTP/1.1
Host: company.example
Origin: https://emailclient.example
AMP-Email-Sender: sender@company.example
User-Agent: EmailClientProxy
Accept: application/json
Response headers expected
AMP-Email-Allow-Sender: sender@company.example
Explanation
Both Origin
and AMP-Email-Sender
are set, indicating that the client supports both versions. Because version 2 takes precedence, only the AMP-Email-Allow-Sender
header is set and Origin
and the value of __amp_source_origin
can be safely ignored.
Code examples
PHP
if (isset($_SERVER['HTTP_AMP_EMAIL_SENDER'])) {
$senderEmail = $_SERVER['HTTP_AMP_EMAIL_SENDER'];
if (!isAllowedSender($senderEmail)) {
die('invalid sender');
}
header("AMP-Email-Allow-Sender: $senderEmail");
} elseif (isset($_SERVER['HTTP_ORIGIN'])) {
$requestOrigin = $_SERVER['HTTP_ORIGIN'];
if (empty($_GET['__amp_source_origin'])) {
die('invalid request');
}
$senderEmail = $_GET['__amp_source_origin'];
if (!isAllowedSender($senderEmail)) {
die('invalid sender');
}
header("Access-Control-Allow-Origin: $requestOrigin");
header('Access-Control-Expose-Headers: AMP-Access-Control-Allow-Source-Origin');
header("AMP-Access-Control-Allow-Source-Origin: $senderEmail");
} else {
die('invalid request');
}
Python (Django)
response = JsonResponse(...)
if request.META.HTTP_AMP_EMAIL_SENDER:
senderEmail = request.META.HTTP_AMP_EMAIL_SENDER
if not isAllowedSender(senderEmail):
raise PermissionDenied
response['AMP-Email-Allow-Sender'] = senderEmail
elif request.META.HTTP_ORIGIN:
requestOrigin = request.META.HTTP_ORIGIN
senderEmail = request.GET.get('__amp_source_origin')
if not isAllowedSender(senderEmail):
raise PermissionDenied
response['Access-Control-Allow-Origin'] = requestOrigin
response['Access-Control-Expose-Headers'] = 'AMP-Access-Control-Allow-Source-Origin'
response['AMP-Access-Control-Allow-Source-Origin'] = senderEmail
else
raise PermissionDenied
SSJS
<script runat="server" language="JavaScript">
Platform.Load("core", "1");
if (Platform.Request.GetRequestHeader("AMP-Email-Sender")) {
var senderEmail = Platform.Request.GetRequestHeader("AMP-Email-Sender")
if (isValidSender(senderEmail)) {
HTTPHeader.SetValue("AMP-Email-Allow-Sender", senderEmail)
} else {
Platform.Function.RaiseError("Sender Not Allowed",true,"statusCode","3");
}
} else if (Platform.Request.GetRequestHeader("Origin")) {
var requestOrigin = Platform.Request.GetRequestHeader("Origin")
if (Platform.Request.GetQueryStringParameter("__amp_source_origin")) {
var senderEmail = Platform.Request.GetQueryStringParameter("__amp_source_origin");
if (isValidSender(senderEmail)) {
HTTPHeader.SetValue("Access-Control-Allow-Origin", requestOrigin);
HTTPHeader.SetValue("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin");
HTTPHeader.SetValue("AMP-Access-Control-Allow-Source-Origin", senderEmail);
} else {
Platform.Function.RaiseError("Invalid Source Origin",true,"statusCode","3");
}
} else {
Platform.Function.RaiseError("Source Origin Not Present",true,"statusCode","3");
}
} else {
Platform.Function.RaiseError("Origin and Sender Not Present",true,"statusCode","3");
}
</script>
Visit Salesforce Developer Documentation to learn more about SSJS.
Node.js
Use the officially supported @ampproject/toolbox-cors npm package.
-
Written by @fstanis