Skip to content

Commit 879d703

Browse files
committed
replace sendgrid with aws ses
1 parent 273182a commit 879d703

File tree

3 files changed

+78
-34
lines changed

3 files changed

+78
-34
lines changed

.env.example

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
DEBUG=True
2-
SENDGRIDFROMEMAIL=''
3-
SENDGRIDAPIKEY=''
2+
AWS_ACCESS_KEY_ID=''
3+
AWS_SECRET_ACCESS_KEY=''
4+
AWS_REGION='us-east-1'
5+
SES_FROM_EMAIL=''
46
RECAPTCHASITEKEY=''
5-
RECAPTCHASECRETKEY=''
7+
RECAPTCHASECRETKEY=''

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Flask==2.2.2
22
Flask-ReCaptcha==0.4.2
33
Jinja2==3.0.3
44
python-dotenv==0.21.0
5-
sendgrid==6.9.7
5+
boto3==1.26.137
66
gunicorn==20.1.0
77
Werkzeug==2.2.2
88
Flask-Limiter==3.8.0

server.py

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
from flask_limiter import Limiter
1010
from flask_limiter.util import get_remote_address
1111

12-
from sendgrid import SendGridAPIClient
13-
from sendgrid.helpers.mail import Mail, Attachment, FileContent, FileName, FileType, Disposition
12+
import boto3
13+
from botocore.exceptions import ClientError
1414

1515
from dotenv import load_dotenv
1616

@@ -74,32 +74,42 @@ def get_identifier(recipient, now=None, randint=None):
7474

7575
def create_email(to_email, identifier, text, all_attachments, reference=''):
7676
"""
77-
Creates an email message with attachments.
77+
Creates an email message with attachments for AWS SES.
7878
"""
79+
from email.mime.multipart import MIMEMultipart
80+
from email.mime.text import MIMEText
81+
from email.mime.application import MIMEApplication
82+
7983
plain_text = text.replace('<br />', '\n')
8084
subject = f'Secure Form Submission {identifier}'
8185
if reference:
8286
subject = f'{reference} {subject}'
8387

84-
message = Mail(
85-
from_email=FROMEMAIL,
86-
to_emails=to_email,
87-
subject=subject,
88-
plain_text_content=plain_text)
89-
88+
# Create message container
89+
msg = MIMEMultipart()
90+
msg['Subject'] = subject
91+
msg['From'] = FROMEMAIL
92+
msg['To'] = to_email
93+
94+
# Add body to email
95+
body = MIMEText(plain_text, 'plain')
96+
msg.attach(body)
97+
98+
# Add attachments
9099
for item in all_attachments:
91100
filename = item['filename']
92-
attachment = item['attachment']
93-
94-
encoded_file = base64.b64encode(attachment.encode("utf-8")).decode()
95-
attachedFile = Attachment(
96-
FileContent(encoded_file),
97-
FileName(filename + '.pgp'),
98-
FileType('application/pgp-encrypted'),
99-
Disposition('attachment')
101+
attachment_content = item['attachment']
102+
103+
# Create attachment
104+
part = MIMEApplication(attachment_content.encode('utf-8'))
105+
part.add_header(
106+
'Content-Disposition',
107+
'attachment',
108+
filename=f'{filename}.pgp'
100109
)
101-
message.add_attachment(attachedFile)
102-
return message
110+
msg.attach(part)
111+
112+
return msg
103113

104114
def validate_recaptcha(recaptcha_response):
105115
"""
@@ -125,17 +135,39 @@ def validate_recaptcha(recaptcha_response):
125135

126136
def send_email(message):
127137
"""
128-
Sends the email using SendGrid and logs detailed information for debugging.
138+
Sends the email using AWS SES and logs detailed information for debugging.
129139
"""
130140
try:
131-
sg = SendGridAPIClient(SENDGRIDAPIKEY)
132-
response = sg.send(message)
133-
logging.info('SendGrid response status code: %s', response.status_code)
134-
if response.status_code not in [200, 201, 202]:
135-
logging.error('SendGrid failed with status code: %s, response body: %s', response.status_code, response.body)
136-
raise ValueError(f"Error: Failed to send email. Status code: {response.status_code}, body: {response.body}")
141+
# Send the email
142+
response = ses_client.send_raw_email(
143+
Source=message['From'],
144+
Destinations=[message['To']],
145+
RawMessage={
146+
'Data': message.as_string()
147+
}
148+
)
149+
150+
# Log the response
151+
message_id = response['MessageId']
152+
logging.info('AWS SES email sent successfully. MessageId: %s', message_id)
153+
154+
except ClientError as e:
155+
error_code = e.response['Error']['Code']
156+
error_message = e.response['Error']['Message']
157+
logging.error('AWS SES error: Code=%s, Message=%s', error_code, error_message)
158+
159+
# Provide user-friendly error messages
160+
if error_code == 'MessageRejected':
161+
raise ValueError('Error: Email was rejected by AWS SES. Please check the email configuration.')
162+
elif error_code == 'MailFromDomainNotVerified':
163+
raise ValueError('Error: The sender email domain is not verified in AWS SES.')
164+
elif error_code == 'ConfigurationSetDoesNotExist':
165+
raise ValueError('Error: AWS SES configuration error.')
166+
else:
167+
raise ValueError(f'Error: Failed to send email. {error_message}')
168+
137169
except Exception as e:
138-
logging.error('Error sending email via SendGrid: %s', str(e))
170+
logging.error('Error sending email via AWS SES: %s', str(e))
139171
raise
140172

141173

@@ -155,13 +187,23 @@ def get_forwarded_address():
155187
return get_remote_address()
156188

157189
# Validate required environment variables
158-
required_env_vars = ['RECAPTCHASITEKEY', 'RECAPTCHASECRETKEY', 'SENDGRIDAPIKEY', 'SENDGRIDFROMEMAIL']
190+
required_env_vars = ['RECAPTCHASITEKEY', 'RECAPTCHASECRETKEY', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION', 'SES_FROM_EMAIL']
159191
validate_env_vars(required_env_vars)
160192

161193
RECAPTCHASITEKEY = os.environ['RECAPTCHASITEKEY']
162194
RECAPTCHASECRETKEY = os.environ['RECAPTCHASECRETKEY']
163-
SENDGRIDAPIKEY = os.environ['SENDGRIDAPIKEY']
164-
FROMEMAIL = os.environ['SENDGRIDFROMEMAIL']
195+
AWS_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID']
196+
AWS_SECRET_ACCESS_KEY = os.environ['AWS_SECRET_ACCESS_KEY']
197+
AWS_REGION = os.environ['AWS_REGION']
198+
FROMEMAIL = os.environ['SES_FROM_EMAIL']
199+
200+
# Initialize AWS SES client
201+
ses_client = boto3.client(
202+
'ses',
203+
region_name=AWS_REGION,
204+
aws_access_key_id=AWS_ACCESS_KEY_ID,
205+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY
206+
)
165207

166208
app = Flask(__name__)
167209
app.config.from_object(Config)

0 commit comments

Comments
 (0)