0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-13 21:30:30 -05:00

refactor(connector): support mailgun endpoint (#4290)

This commit is contained in:
Gao Sun 2023-08-03 17:23:27 +08:00 committed by GitHub
parent 56b0a2cd18
commit 521b3a5c51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 14 deletions

View file

@ -21,6 +21,7 @@ The official Logto connector for Mailgun email service.
## Basic configuration
- Fill out the `endpoint` field when you are using a different Mailgun API endpoint, for example, EU region should be `https://api.eu.mailgun.net`. The default value is `https://api.mailgun.net`.
- Fill out the `domain` field with the domain you have registered in your Mailgun account. This value can be found in the **Domains** section of the Mailgun dashboard. The domain should be in the format `example.com`, without the `https://` or `http://` prefix.
- Fill out the `apiKey` field with the API key you have generated in your Mailgun account.
- Fill out the `from` field with the email address you want to send emails from. This email address must be registered in your Mailgun account. The email address should be in the format `Sender Name <sender@example.com>`.

View file

@ -17,12 +17,19 @@ export const defaultMetadata: ConnectorMetadata = {
},
readme: './README.md',
formItems: [
{
key: 'endpoint',
label: 'Mailgun endpoint',
type: ConnectorConfigFormItemType.Text,
required: false,
placeholder: 'https://api.mailgun.net',
},
{
key: 'domain',
label: 'Domain',
type: ConnectorConfigFormItemType.Text,
required: true,
placeholder: 'https://your-mailgun-domain.com',
placeholder: 'your-mailgun-domain.com',
},
{
key: 'apiKey',

View file

@ -25,8 +25,11 @@ const baseConfig: Partial<MailgunConfig> = {
*
* @param expectation - The expected request body.
*/
const nockMessages = (expectation: Record<string, string | string[] | undefined>) =>
nock('https://api.mailgun.net')
const nockMessages = (
expectation: Record<string, string | string[] | undefined>,
endpoint = 'https://api.mailgun.net'
) =>
nock(endpoint)
.post(`/v3/${domain}/messages`)
.basicAuth({ user: 'api', pass: apiKey })
.reply((_, body, callback) => {
@ -104,6 +107,39 @@ describe('Maligun connector', () => {
});
});
it('should send email with template and EU endpoint', async () => {
nockMessages(
{
from: 'foo@example.com',
to: 'bar@example.com',
subject: 'Verification code is 123456',
template: 'template',
'h:X-Mailgun-Variables': JSON.stringify({ foo: 'bar', code: '123456' }),
},
'https://api.eu.mailgun.net'
);
getConfig.mockResolvedValue({
...baseConfig,
endpoint: 'https://api.eu.mailgun.net',
deliveries: {
[VerificationCodeType.Generic]: {
template: 'template',
variables: { foo: 'bar' },
subject: 'Verification code is {{code}}',
},
},
});
await connector.sendMessage({
to: 'bar@example.com',
type: VerificationCodeType.Generic,
payload: {
code: '123456',
},
});
});
it('should fall back to generic template if type not found', async () => {
nockMessages({
from: 'foo@example.com',

View file

@ -49,7 +49,7 @@ const sendMessage = (getConfig: GetConnectorConfig): SendMessageFunction => {
const config = inputConfig ?? (await getConfig(defaultMetadata.id));
validateConfig(config, mailgunConfigGuard);
const { domain, apiKey, from, deliveries } = config;
const { endpoint, domain, apiKey, from, deliveries } = config;
const type = supportTemplateGuard.safeParse(typeInput);
if (!type.success) {
@ -63,7 +63,9 @@ const sendMessage = (getConfig: GetConnectorConfig): SendMessageFunction => {
}
try {
return await got.post(`https://api.mailgun.net/v3/${domain}/messages`, {
return await got.post(
new URL(`/v3/${domain}/messages`, endpoint ?? 'https://api.mailgun.net').toString(),
{
username: 'api',
password: apiKey,
form: {
@ -71,7 +73,8 @@ const sendMessage = (getConfig: GetConnectorConfig): SendMessageFunction => {
to,
...removeUndefinedKeys(getDataFromDeliveryConfig(template, code)),
},
});
}
);
} catch (error) {
if (error instanceof HTTPError) {
const {

View file

@ -48,7 +48,7 @@ describe('Mailgun config guard', () => {
expect(() => mailgunConfigGuard.parse(validConfig)).not.toThrow();
});
it('should fail with invalid config', () => {
it('should fail with invalid delivery config', () => {
const invalidConfig = {
domain: 'example.com',
apiKey: 'key',
@ -62,4 +62,21 @@ describe('Mailgun config guard', () => {
};
expect(() => mailgunConfigGuard.parse(invalidConfig)).toThrow();
});
it('should fail with invalid endpoint', () => {
const invalidConfig = {
endpoint: 'https://api.mailgun1.net',
domain: 'example.com',
apiKey: 'key',
from: 'from',
deliveries: {
[VerificationCodeType.ForgotPassword]: {
html: 'html',
subject: 'subject',
},
},
};
expect(() => mailgunConfigGuard.parse(invalidConfig)).toThrow();
});
});

View file

@ -53,6 +53,8 @@ const templateConfigGuard = z.union([
]) satisfies z.ZodType<DeliveryConfig>;
export type MailgunConfig = {
/** Mailgun endpoint. For EU region, use `https://api.eu.mailgun.net`. */
endpoint?: string;
/** Mailgun domain. */
domain: string;
/** Mailgun API key. */
@ -67,6 +69,7 @@ export type MailgunConfig = {
};
export const mailgunConfigGuard = z.object({
endpoint: z.string().url().endsWith('.mailgun.net').optional(),
domain: z.string(),
apiKey: z.string(),
from: z.string(),